YAML 2: Multiline String'ler, Anchor/Alias, Merge Key, Çoklu Doküman ve KYAML
Script'i | ile, uzun metni > ile yaz; secret gibi tek satırlık değerlerde |- kullan, yoksa sona \n eklenir. Merge key (<<) YAML 1.2'de yok: Compose, kubectl ve kubeconform destekliyor, GitHub Actions desteklemiyor, js-yaml 5 ise ayar istiyor. Çoklu doküman, anchor'ın doküman sınırı, güvensiz tag'ler, KYAML ve Bookstore için bir yamllint kural setini gerçek çıktılarla gösteriyoruz.
Sürüm notu: Bu yazıdaki örnekler yq 4.54.3, PyYAML 6.0.3, js-yaml 5.5.0, yaml 2.9.1, yamllint 1.38.0, actionlint 1.7.12, Docker Compose 5.1, kubectl 1.37 (yerel bir kind cluster'ına karşı) ve kubeconform 0.8.0 ile çalıştırılmıştır. Spec olarak 1.2.2 sürümü esas alınmıştır.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) release workflow'una iki satırlık bir step ekliyoruz. Satırlar uzun olmasın diye > kullandık:
# Bookstore release workflow'undan bir step
steps:
- name: Show build info
run: >
echo "Building Bookstore.Api"
echo "Commit: abc1234"
GitHub Actions bu step'i çalıştırdığında shell'e kaç komut gider ve ekrana ne yazılır? > yerine | yazsaydık ne değişirdi?
Block Scalar'lar: Literal, Folded ve Chomping
Tahminin cevabı, step'teki metni yq ile string olarak okuyup bash'e verince görünüyor:
$ yq -o json '.steps[0].run' publish-step.yaml
"echo \"Building Bookstore.Api\" echo \"Commit: abc1234\"\n"
$ bash -c "$(yq '.steps[0].run' publish-step.yaml)"
Building Bookstore.Api echo Commit: abc1234
İki komut tek satıra birleşti ve bash tek bir echo çalıştırdı; ikinci echo ekrana yazılan metnin parçası oldu. Sebep block scalar'ın türü. YAML'da (girintiye dayalı, elle yazılmaya uygun veri formatı) çok satırlı string yazmanın iki yolu var: | (literal) satır sonlarını olduğu gibi korur, > (folded) ise satır sonlarını boşluğa çevirir. Folded'ın iki istisnası var: boş satır bir satır sonu olarak kalır ve daha fazla girintili satırların etrafındaki satır sonları korunur. Script için doğru seçim her zaman |; > uzun bir açıklama metnini dosyada birkaç satıra bölmek için var.
Sonda kalan satır sonunu chomping indicator belirler. Varsayılan (clip) tam olarak bir \n bırakır, - (strip) hepsini siler, + (keep) sondaki boş satırların hepsini korur:
literal: |
line one
line two
folded: >
line one
line two
new paragraph
indented line
strip: |-
no trailing newline
keep: |+
all trailing newlines
clip: |
exactly one trailing newline
$ yq -o json block-scalars.yaml
{
"literal": "line one\nline two\n",
"folded": "line one line two\nnew paragraph\n indented line\n",
"strip": "no trailing newline",
"keep": "all trailing newlines\n\n",
"clip": "exactly one trailing newline\n"
}
Bu fark en çok secret'larda can yakar. Bookstore'un veritabanı şifresini bir Kubernetes Secret'ına | ile yazarsan şifrenin sonuna \n eklenir:
apiVersion: v1
kind: Secret
metadata:
name: api-db
stringData:
password: |
example-password-change-me
passwordStripped: |-
example-password-change-me
$ kubectl apply --dry-run=client -f secret.yaml -o json | jq -c '.stringData'
{"password":"example-password-change-me\n","passwordStripped":"example-password-change-me"}
Uygulama şifreyi environment variable ya da dosya olarak okuduğunda sondaki \n şifrenin parçası olur ve veritabanı girişi reddeder; log'da gördüğün şifre ise doğru görünür. Tek satırlık değerler için block scalar kullanma, kullanman gerekiyorsa |- yaz.
Üçüncü ayar indentation indicator. Parser bloğun girintisini ilk dolu satırdan çıkarır. İlk satır kasıtlı olarak daha fazla girintiliyse (ör. hizalı bir metin) girintiyi bir rakamla açıkça vermen gerekir; rakam, bloğun üst key'e göre kaç boşluk içeride olduğunu söyler:
# notun ilk satırı boşlukla başlıyor, bu yüzden girinti açıkça verilmeli
invoiceNote: |2
Institutional order: 25 copies
Discount: 10%
$ yq -o json -I0 indent-indicator.yaml
{"invoiceNote":" Institutional order: 25 copies\n Discount: 10%\n"}
$ sed 's/|2/|/' indent-indicator.yaml | yq -o json -I0 .
Error: bad file '-': go-yaml load error in parser (while parsing a block mapping) at L2.C1-L4.C5: did not find expected key
Rakamsız hâlde parser girintiyi ilk satırdan (altı boşluk) aldı, dört boşluklu ikinci satırı bloğun dışında saydı ve dosya geçersiz oldu.
Script ve Config Gömmek: run: | ve ConfigMap
Literal block'un en sık kullanıldığı iki yer GitHub Actions'ın run: alanı ve Kubernetes ConfigMap'leri. Bookstore'un manifest dosyasında Bookstore.Api'nin production ayarları bir ConfigMap'in içinde, olduğu gibi duruyor; Deployment bu dosyayı /app/appsettings.Production.json olarak mount ediyor:
$ yq 'select(.kind == "ConfigMap")' deploy/k8s/api.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
data:
appsettings.Production.json: |
{
"Catalog": {
"PageSize": 20,
"MaxPageSize": 100
},
"Shipping": {
"DefaultCountry": "NO"
}
}
$ kubectl apply -f deploy/k8s/api.yaml
configmap/api-config created
deployment.apps/api created
service/api created
$ kubectl get configmap api-config -o jsonpath='{.data.appsettings\.Production\.json}' | jq -c .
{"Catalog":{"PageSize":20,"MaxPageSize":100},"Shipping":{"DefaultCountry":"NO"}}
Gömülü JSON'un girintisi YAML'ın girintisinden bağımsız: parser block'un ortak girintisini (dört boşluk) atıyor, geriye JSON'un kendi girintisi kalıyor. ConfigMap'ten geri okuduğumuz metin jq'nun sorunsuz parse ettiği geçerli bir JSON. Gömülü config uzarsa onu ayrı bir dosyada tutup Kustomize'ın configMapGenerator'ı ile üretmek daha bakımı kolay bir yol; bu Kubernetes serisinin Kustomize yazısında.
Anchor, Alias ve Merge Key
Anchor (&ad) bir node'a isim verir, alias (*ad) aynı node'u başka bir yerde tekrar kullanır. Merge key (<<) ise bir mapping'in key'lerini başka bir mapping'e kopyalar ve üzerine yazmana izin verir. Bookstore'da iki HTTP client'ın retry ayarı (aşağıdaki merge.mjs için klasörde önce npm install çalıştır):
# İki HTTP client için aynı retry policy
defaults: &retry
maxAttempts: 3
delayMs: 200
payments:
retry: *retry
shipping:
retry:
<<: *retry
delayMs: 500
$ yq --yaml-fix-merge-anchor-to-spec -o json -I0 'explode(.) | .shipping' anchors.yaml
{"retry":{"maxAttempts":3,"delayMs":500}}
$ python -c 'import yaml, json; print(json.dumps(yaml.safe_load(open("anchors.yaml"))["shipping"]))'
{"retry": {"maxAttempts": 3, "delayMs": 500}}
$ node merge.mjs anchors.yaml
js-yaml {"retry":{"<<":{"maxAttempts":3,"delayMs":200},"delayMs":500}}
js-yaml (1.1) {"retry":{"maxAttempts":3,"delayMs":500}}
yaml {"retry":{"<<":{"maxAttempts":3,"delayMs":200},"delayMs":500}}
yaml (merge) {"retry":{"maxAttempts":3,"delayMs":500}}
Anchor ve alias YAML 1.2 spec'inin parçası, her parser destekler. Merge key ise spec'te yok: 2005'te YAML 1.1 için yayımlanan tip listesinde tanımlanmış ayrı bir özellik. Bu yüzden desteği kütüphaneye bağlı. PyYAML destekliyor. js-yaml 5 ise varsayılan olarak YAML 1.2 core schema'yı kullanıyor ve <<'ü sıradan bir key gibi bırakıyor; birleştirme için YAML11_SCHEMA'yı açıkça vermen gerekiyor. yaml paketi de merge: true istiyor. yq birleştirmeyi yapıyor ama 4.54.3'te her çalışmada bir uyarı basıyor: --yaml-fix-merge-anchor-to-spec bayrağı olmadan, birden fazla merge kaynağı olduğunda spec'ten farklı bir öncelik sırası uyguluyor. Yardım metni bu bayrağın 2025 sonunda varsayılan olacağını söylüyor ama bu sürümde hâlâ kapalı.
Compose, Kubernetes ve GitHub Actions'ta Merge Key
Plan "araçlar destekliyor mu?" sorusunu tek tek denemekti. Compose'da x- ile başlayan üst seviye alanlar extension field'dır, Compose onları yok sayar; anchor'ı burada tanımlamak yaygın bir kalıp:
name: bookstore
x-api-env: &api-env
ASPNETCORE_ENVIRONMENT: Development
Catalog__PageSize: "20"
services:
api:
image: bookstore-api:dev
environment:
<<: *api-env
Shipping__DefaultCountry: "NO"
worker:
image: bookstore-worker:dev
environment:
<<: *api-env
Catalog__PageSize: "50"
$ docker compose -f compose-merge/compose.yaml config | yq '.services[].environment'
ASPNETCORE_ENVIRONMENT: Development
Catalog__PageSize: "20"
Shipping__DefaultCountry: "NO"
ASPNETCORE_ENVIRONMENT: Development
Catalog__PageSize: "50"
Compose merge key'i destekliyor; worker service'i ortak ayarları alıp Catalog__PageSize'ı ezdi. Bunu Compose'un birden fazla dosyayı birleştirme kurallarıyla karıştırma: o mekanizma (override dosyaları, !override ve !reset tag'leri) Docker Compose serisinin birden çok environment yazısında. Kubernetes tarafı:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
selector:
matchLabels: &labels
app.kubernetes.io/name: bookstore-api
template:
metadata:
labels:
<<: *labels
app.kubernetes.io/version: "1.10"
spec:
containers:
- name: api
image: bookstore-api:1.10.0
$ kubectl apply --dry-run=server -f deploy-merge.yaml -o json | jq -c '.spec.template.metadata.labels'
{"app.kubernetes.io/name":"bookstore-api","app.kubernetes.io/version":"1.10"}
$ kubeconform -summary deploy-merge.yaml
Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0, Skipped: 0
kubectl ve kubeconform da merge key'i destekliyor, çünkü YAML'ı JSON'a çevirirken YAML 1.1 özelliklerini bilen bir kütüphane kullanıyorlar. GitHub Actions'ta durum farklı:
name: merge-key
on: workflow_dispatch
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
env: &dotnet-env
DOTNET_NOLOGO: "true"
DOTNET_CLI_TELEMETRY_OPTOUT: "true"
steps:
- run: dotnet --info
publish:
runs-on: ubuntu-latest
env:
<<: *dotnet-env
CONFIGURATION: Release
steps:
- run: dotnet publish src/Bookstore.Api -c "$CONFIGURATION"
$ actionlint .github/workflows/merge.yml
.github/workflows/merge.yml:19:7: GitHub Actions does not support YAML merge key "<<" [syntax-check]
|
19 | <<: *dotnet-env
| ^~~
GitHub Actions Eylül 2025'ten beri anchor ve alias'ı destekliyor, ama dokümanı yalnızca bu ikisini anlatıyor; merge key'i actionlint açıkça reddediyor. Yani GitHub Actions'ta bir bloğu ancak olduğu gibi tekrar kullanabilirsin, "aynısı, şu farkla" diyemezsin. Bunun yerine ne yapılacağı (composite action, reusable workflow) GitHub Actions serisinin tekrarı azaltma yazısında.
Çoklu Doküman, Anchor'ın Sınırı ve Tag'ler
--- bir dosyada yeni bir YAML dokümanı başlatır. Kubernetes manifest'lerinde bir uygulamanın ConfigMap, Deployment ve Service'ini tek dosyada tutmak bu sayede mümkün. yq her dokümanı ayrı işler:
$ yq -N '.kind' deploy/k8s/api.yaml
ConfigMap
Deployment
Service
$ yq 'select(.kind == "Service") | .spec.ports' deploy/k8s/api.yaml
- name: http
port: 80
targetPort: http
$ kubectl get -f deploy/k8s/api.yaml -o name
configmap/api-config
deployment.apps/api
service/api
Anchor'ın kapsamı tek doküman. Spec'e göre bir alias yalnızca aynı dokümanda daha önce tanımlanmış bir anchor'a işaret edebilir; dosyalar arasında ise zaten hiçbir bağlantı yok. Ama araçlar bu kuralı aynı sıkılıkta uygulamıyor:
# anchor ilk dokümanda tanımlı, alias ikincide kullanılıyor
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
labels: &labels
app.kubernetes.io/part-of: bookstore
---
apiVersion: v1
kind: Service
metadata:
name: api
labels: *labels
$ kubectl apply --dry-run=server -f cross-doc.yaml
configmap/api-config configured (server dry run)
error: error parsing cross-doc.yaml: error converting YAML to JSON: yaml: unknown anchor 'labels' referenced
$ python -c 'import yaml; list(yaml.safe_load_all(open("cross-doc.yaml")))' 2>&1 | tail -2
yaml.composer.ComposerError: found undefined alias 'labels'
in "cross-doc.yaml", line 13, column 11
$ yq -o json -I0 '.metadata.labels' cross-doc.yaml
{"app.kubernetes.io/part-of":"bookstore"}
{"app.kubernetes.io/part-of":"bookstore"}
kubectl ilk dokümanı uyguladı, ikincide durdu; PyYAML de hata verdi. yq ise anchor'ı dokümanlar arasında çözüp devam etti. Dosyayı yq ile test edip "çalışıyor" demek, kubectl'de yarım kalan bir deploy'a dönüşebilir: dosyadaki ilk kaynak uygulandı, ikincisi uygulanmadı.
Tag'ler bir değerin tipini açıkça söyler. !!str implicit typing'i devre dışı bırakır ve her parser'da aynı sonucu verir; tırnağın uzun yazılışı gibi düşünebilirsin. Bazı kütüphaneler kendi tag'lerini de tanır ve tehlike burada başlar. PyYAML'ın !!python/object/apply tag'i bir Python fonksiyonunu çağırır:
apiVersion: !!str 1.10
isbn: !!str 9780441013593
unsafe: !!python/object/apply:os.system ["echo this should not run"]
$ python -c 'import yaml; yaml.safe_load(open("tags.yaml"))' 2>&1 | tail -2
yaml.constructor.ConstructorError: could not determine a constructor for the tag 'tag:yaml.org,2002:python/object/apply:os.system'
in "tags.yaml", line 3, column 9
$ head -2 tags.yaml | python -c 'import yaml, sys; print(yaml.safe_load(sys.stdin))'
{'apiVersion': '1.10', 'isbn': '9780441013593'}
$ head -2 tags.yaml | yq -o json -I0 .
{"apiVersion":"1.10","isbn":"9780441013593"}
safe_load bu tag'i tanımadığı için reddetti. yaml.load'u UnsafeLoader ile çağırsaydık, YAML'ı okumak bir komut çalıştırmak demek olurdu. Dışarıdan gelen YAML'ı her zaman güvenli modda oku; deserialization saldırılarının ayrıntısı Web Güvenliği (OWASP Top 10) serisinin A08 yazısında. .NET'te YamlDotNet de tag'leri yalnızca senin kaydettiğin tiplere eşler.
KYAML: Kubernetes'in Katı YAML'ı
KYAML (Kubernetes'in tanımladığı, daha katı bir YAML alt kümesi) önceki yazıdaki sorunlara Kubernetes'in cevabı: her string çift tırnaklı, yapılar flow style'da ({ } ve [ ]), girintinin anlamı yok. Sonuç yine geçerli YAML, yani her parser okuyabiliyor. KYAML 1.34'te alpha olarak geldi, 1.35'te varsayılan olarak açıldı ve 1.37'de stable oldu; kubectl'de -o kyaml ile çıktı alırsın:
$ kubectl create configmap shipping --from-literal=DefaultCountry=NO --from-literal=ApiVersion=1.10 --dry-run=client -o yaml
apiVersion: v1
data:
ApiVersion: "1.10"
DefaultCountry: "NO"
kind: ConfigMap
metadata:
name: shipping
$ kubectl create configmap shipping --from-literal=DefaultCountry=NO --from-literal=ApiVersion=1.10 --dry-run=client -o kyaml
---
{
kind: "ConfigMap",
apiVersion: "v1",
metadata: {
name: "shipping",
},
data: {
ApiVersion: "1.10",
DefaultCountry: "NO",
},
}
Normal YAML çıktısı da "NO" ve "1.10"'u tırnaklıyor, çünkü kubectl string olduğunu bildiği değerleri güvenli yazıyor. KYAML'ın farkı kuralın tek ve sabit olması: string ise tırnaklı, nokta. yq de KYAML yazabiliyor; ama burada bir ayrıntı var:
$ yq -o kyaml ../02-yaml/shipping.yaml | tee shipping.kyaml
# Bookstore kargo ayarları
{
shipping: {
countries: [
"TR",
"DE",
"NO",
],
cutoff: "12:30",
express: "yes",
giftWrap: "on",
labelFileMode: 0755,
maxWeightGrams: 1e3,
apiVersion: 1.10,
note: null,
},
}
$ python ../02-yaml/parse.py shipping.kyaml
PyYAML 6.0.3 {"shipping": {"countries": ["TR", "DE", "NO"], "cutoff": "12:30", "express": "yes", "giftWrap": "on", "labelFileMode": 493, "maxWeightGrams": "1e3", "apiVersion": 1.1, "note": null}}
ruamel.yaml {"shipping": {"countries": ["TR", "DE", "NO"], "cutoff": "12:30", "express": "yes", "giftWrap": "on", "labelFileMode": 755, "maxWeightGrams": 1000.0, "apiVersion": 1.1, "note": null}}
yq string'leri tırnakladı, bu yüzden PyYAML artık Norveç'i doğru okuyor. Ama 0755, 1e3 ve 1.10'u dosyada nasıl yazılmışsa öyle bıraktı ve iki parser yine farklı sonuç verdi. KYAML, değer zaten doğru tiple elindeyse (kubectl'in API'den aldığı object gibi) güvenli bir yazım biçimi; yanlış tiple yazılmış bir dosyayı kendiliğinden düzeltmiyor. Manifest'lerini KYAML'a çevirmen gerekmiyor; Kubernetes serisinin 1.33–1.37 yenilikleri yazısında da anlatıldığı gibi asıl kullanım yeri çıktıyı incelemek.
yamllint ile Kural Seti ve CI
Buraya kadarki tuzakların çoğunu bir linter yakalayabilir. yamllint Python ile yazılmış, kuralları bir YAML dosyasında tanımlanan bir linter. Varsayılan kural seti Bookstore'un dosyalarında document-start (her dosya --- ile başlasın) ve on: için truthy uyarısı veriyor. Bookstore için ayarladığımız kural seti:
extends: default
rules:
# Kubernetes ve Compose dosyaları nadiren --- ile başlar
document-start: disable
# GitHub Actions'taki "on:" key'i sorun değil; değerler true/false olmalı
truthy:
allowed-values: ["true", "false"]
check-keys: false
level: error
# 0755, YAML 1.1'de 493, YAML 1.2'de 755
octal-values:
forbid-implicit-octal: true
line-length:
max: 120
anchors:
forbid-unused-anchors: true
$ yamllint . && echo "bookstore: no findings"
bookstore: no findings
$ yamllint ../labs/02-yaml/shipping.yaml ../labs/02-yaml/compose.yaml ../labs/03-yaml/cross-doc.yaml
../labs/02-yaml/shipping.yaml
3:23 error truthy value should be one of [false, true] (truthy)
5:12 error truthy value should be one of [false, true] (truthy)
6:13 error truthy value should be one of [false, true] (truthy)
7:22 error forbidden implicit octal value "0755" (octal-values)
../labs/02-yaml/compose.yaml
8:33 error truthy value should be one of [false, true] (truthy)
9:26 error truthy value should be one of [false, true] (truthy)
11:36 error forbidden implicit octal value "0755" (octal-values)
../labs/03-yaml/cross-doc.yaml
6:11 error found unused anchor "labels" (anchors)
13:11 error found undeclared alias "labels" (anchors)
Bookstore'un kendi dosyaları temiz; önceki yazının tırnaksız dosyalarında ise Norveç, yes, on ve 0755 yakalandı, dokümanlar arası anchor da. yamllint'in yakalamadığını da bil: 1.10 ve 12:30 gibi değerleri doğrudan yakalayan bir kuralı yok. Bunları ancak YAML'ı okuyacak aracın kendisi (kubeconform, docker compose config) ya da şema doğrulaması yakalar; şemayı serinin beşinci yazısında göreceğiz. CI'da -f github çıktısı her bulguyu pull request'te ilgili satıra bir annotation olarak düşer:
$ yamllint -f github ../labs/02-yaml/compose.yaml
::group::../labs/02-yaml/compose.yaml
::error file=../labs/02-yaml/compose.yaml,line=8,col=33::8:33 [truthy] truthy value should be one of [false, true]
::error file=../labs/02-yaml/compose.yaml,line=9,col=26::9:26 [truthy] truthy value should be one of [false, true]
::error file=../labs/02-yaml/compose.yaml,line=11,col=36::11:36 [octal-values] forbidden implicit octal value "0755"
::endgroup::
name: ci
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.14"
- run: pip install yamllint==1.38.0
# -f github her bulguyu pull request'te bir annotation'a çevirir
- run: yamllint -f github .
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x
- name: Build
run: |
dotnet restore src/Bookstore.Api
dotnet build src/Bookstore.Api -c Release --no-restore
$ actionlint .github/workflows/ci.yml && echo "actionlint: no findings"
actionlint: no findings
$ kubectl delete -f deploy/k8s/api.yaml
configmap "api-config" deleted from default namespace
deployment.apps "api" deleted from default namespace
service "api" deleted from default namespace
Editor tarafında Visual Studio Code için Red Hat'in YAML extension'ı ve Prettier, YAML dosyalarını kaydederken formatlayabiliyor; ikisini de bu yazı için çalıştırmadım. Formatlayıcı girinti ve boşlukları düzeltir ama tipleri değiştirmez: tırnaksız bir NO formatlandıktan sonra da tırnaksız kalır. Bu yüzden formatlayıcı ile linter ayrı işler yapar, ikisine de ihtiyacın var.
Üretimde Dikkat
Anchor ve merge key bir dosyayı kısaltır ama okunabilirliği düşürür: bir değerin nereden geldiğini anlamak için dosyada yukarı aşağı gezmen gerekir. Bookstore'da kuralımız şu: anchor yalnızca Compose'daki x- blokları gibi açıkça "ortak ayar" diye adlandırılmış yerlerde. Kubernetes'te tekrarı azaltmak için Kustomize veya Helm, GitHub Actions'ta composite action ve reusable workflow daha açık araçlar.
Dışarıdan gelen YAML'da alias'lar bellek tüketimi açısından da risklidir: iç içe alias'larla yazılmış küçük bir dosya, açıldığında çok büyük bir yapıya dönüşebilir. js-yaml 5'in maxTotalMergeKeys gibi sınırlar getirmesi bu yüzden. Kullanıcıdan YAML kabul ediyorsan güvenli loader, boyut sınırı ve mümkünse şema doğrulaması üçünü birlikte kullan.
Son olarak, bir CI pipeline'ında YAML'ı her zaman production'da okuyacak aracın kendisiyle doğrula: Compose için docker compose config, Kubernetes için kubectl apply --dry-run=server veya kubeconform, workflow'lar için actionlint. yq ile "okunabiliyor" demek, cross-document anchor örneğinde gördüğümüz gibi yetmez.
Sık Düşülen Tuzaklar
Script'i > ile yazmak tahmindeki hatanın aynısı ve hata vermediği için fark edilmesi zor; komutların bir kısmı başka bir komutun argümanı olur. Tersi de olur: > ile yazdığın bir açıklama metninde tek bir satırı fazladan girintilersen o satırın etrafındaki satır sonları korunur ve metin beklediğin gibi tek paragraf olmaz.
Secret ve tek satırlık değerlerde | kullanıp sondaki \n'i unutmak; anchor'ın başka bir dosyada ya da başka bir dokümanda da çalışacağını sanmak; GitHub Actions'ta merge key yazıp workflow'un hiç başlamaması. Bir de kopyala-yapıştır: anchor'lı bir bloğu başka bir dosyaya taşıdığında alias'lar tanımsız kalır ve bunu ancak dosyayı okuyan araç söyler.
Küçük Bir Deneme
★☆☆ publish-step.yaml'da > yerine | yaz ve iki komutu tekrar çalıştır. Sonra |'yi |- yap; yq çıktısında ne değişti?
★★☆ Bookstore'un compose.yaml'ına bir worker service'i ekle ve api ile ortak environment variable'larını bir x- bloğu ve merge key ile paylaştır. docker compose config ile iki service'in son hâlini karşılaştır.
★★★ deploy/k8s/api.yaml'ı kubectl apply --dry-run=client -o kyaml ile KYAML'a çevir, sonuç dosyasını PyYAML ve ruamel.yaml ile oku ve iki parser'ın aynı sonucu verdiğini doğrula. Aynı şeyi yq'nun -o kyaml çıktısıyla dene; fark nerede?
YAML'ı iki yazıda bitirdik. Bir sonraki yazıda configuration formatlarının geri kalanına bakacağız: TOML (tipli, okunması kolay bir config formatı), INI (köşeli parantezli bölümlerden oluşan eski config formatı) ve .env dosyaları; aynı Bookstore.Api ayarını her formatta yazıp .NET'in configuration provider'larıyla okuyacağız ve hangi formatın ne zaman seçileceğini bir tabloyla karşılaştıracağız.