今までkubernetesのmanifestはテンプレートエンジンを使って環境毎にビルドして使っていましたが
kubectl1.14にkustomizeが統合された(?)のもあり
kustomizeでビルドする感じに移行したので、やったことをメモ
kustomizeのベスト・プラクティスが知りたい、、、
環境
- GKE
- 1.13系
- kustomize
- v2.0.3
- sops
- 3.3.0
管理方針
-
ディレクトリ構成
- baseとoverlaysで管理する
-
環境変数
- configMapGeneratorとsecretGeneratorを使う
- 環境変数にConfigMapを使うと環境変数を変えた時にdeploymentが自動的にrestartしないのを理由にすべてenvを書いていたのでやっとConfigMapで管理できるようになった
- env部分だけテンプレートファイルを分けて、同じ環境変数を使いたいdeploymentのテンプレートにincludeするような感じで運用していた
-
secret
- mozilla/sopsで暗号化してリポジトリに入れる
-
Deploy
- CloudBuildでbuild & deploy(to GKE)を行う
ディレクトリ構成
.
├── base (各サービスのベースとなるmanifest)
│ ├── service-a
│ │ ├── kustomization.yaml
│ │ └── deployment.yaml
│ ├── service-b
│ │ └── ...
│ ├── kube-state-metrics
│ │ └── ...
│ ├── newrelic-infra
│ │ └── ...
│ └── etc...
└── overlays (各環境毎のmanifest)
├── production
│ ├── kustomization.yaml
│ ├── hpa.yaml
│ └── secret
│ ├── xxx.enc.env
│ └── etc...
└── staging
├── kustomization.yaml
├── hpa.yaml
└── secret
├── xxx.enc.env
└── etc...
-
base
- overlaysのkustomization.yamlから読み込む
- 必要なマイクロサービス的な単位でディレクトリを分ける
- baseはbase単体でビルド可能なようにしておく
- 必要な環境変数はconfigMapGeneratorとsecretGeneratorでダミー値を入れて書いておく
- overlaysのkustomization.yamlで
behavior: merge
で必要な部分だけ書き換える
- overlaysのkustomization.yamlで
- リソースの種類毎にYAMLファイルを分ける
- 複数のDeploymentがある場合などは、
deployments/a.yaml
のようにして分ける場合もある
-
overlays
- 環境毎にディレクトリを分ける
- patch用のyamlはリソースの種類毎に分ける
- こうしておくと
スケールアウトさせたい時はhpa.yamlを見ればいい
など意外と運用時にわかりやすい - secretはsecretGeneratorを使うので、env形式のファイルに分離して暗号化しておく
環境変数
- とくに暗号化の必要ない環境変数はconfigMapGeneratorで管理する
- ConfigMapの名前のsuffixとしてhash値を付けて管理してくれるので、ConfigMapの内容が変わった時にnameが変わることでdeploymentが更新される
- ただし、不要になったConfigMapがゴミとして残りつづける
- rollbackした時などに必要になるのである程度しょうがないが、残す数を設定できると良いかも...
- 暗号化が必要な環境変数はsecretGeneratorでenvファイルを読み込むようにする
Secret
- 読み込むenvファイルは
xxx.enc.env
というファイル名で暗号化して持っておく- 暗号化にはsopsとGCPのCloud KMSで管理している鍵を使う
- ビルド時にsopsで復号化するが、復号化したファイルはgitignoreでgit管理下には置かないようにする
-
envCommand
は使えなくなっていた https://github.com/kubernetes-sigs/kustomize/pull/703
-
- DeploymentやCronJobなどからはenvFromでConfigMap or Secretを読み込む
このあたりモヤモヤするのでベストプラクティスが知りたい、、、
Build
- sopsでenvの復号化が必要だったので、スクリプトを書くことにした
- https://github.com/Agilicus/kustomize-sops こんなのもあるらしい
#!/bin/bash -eu
decrypt_secrets() {
for f in $(ls overlays/${stage}/secret/*.enc.*); do
local output=$(echo $f | sed -e 's/\.enc//')
sops --output $output -d $f
done
}
usage() {
echo $1
cat <<_EOT_
Usage:
`basename $0` stage
Description:
build manifests by kustomize
Options:
stage ビルドするoverlayを指定します。 production, staging, etc...
-h --help HELP
_EOT_
exit 1
}
stage=
for OPT in "$@"
do
case "$OPT" in
# ヘルプメッセージ
'-h'|'--help' )
usage "Help"
exit 1
;;
-*)
usage "[ERROR] Undefined options."
exit 1
;;
*)
# コマンド引数(オプション以外のパラメータ)
if [[ ! -z "$1" ]] && [[ ! "$1" =~ ^-+ ]]; then
stage=$1
shift 1
fi
;;
esac
done
if [ -z "$stage" ]; then
echo -e "\033[0;31mplease select stage. production or staging\033[0m" 1>&2
exit 1
fi
decrypt_secrets \
&& kustomize build overlays/${stage}
- getopts使ってないのは特に理由はありません...
- shell scriptあまり書かないので、細かい所は適当です
Deploy
- deploy実行用のCloudBuildで使うイメージはkustomize入りのイメージを使う
- cloud-builders-community/kustomize at master · GoogleCloudPlatform/cloud-builders-community
- CloudBuildのstepでsopsのバイナリをダウンロードして使う
- production
- gitのpushをトリガーにしてCloudBuildで実行
- 条件
- branch: master
-
base/**/*
oroverlays/production/**/*
に変更がある場合
- staging
-
gcloud builds submit
でローカルからCloudBuildを実行する - ブランチへのpushをトリガーにしてもよかったが、以下の問題があったのでローカルから手動実行する事にした
- staging環境が複数ある場合に、同じ変更を複数のstagingにデプロイしたい場合がある
- ブランチ名を条件にしてしまうと、同じ変更なのに複数のブランチを用意する必要がある
-
cloudbuild.yaml
steps:
- id: build
name: 'gcr.io/$PROJECT_ID/kustomize'
entrypoint: bash
args:
- '-c'
- |
gcloud container clusters get-credentials --zone "$$CLOUDSDK_COMPUTE_ZONE" "$$CLOUDSDK_CONTAINER_CLUSTER" \
&& ./scripts/install-sops-on-cloudbuild.sh \
&& ./kube-apply ${_STAGE}
env:
- 'CLOUDSDK_COMPUTE_ZONE=xxxx'
- 'CLOUDSDK_CONTAINER_CLUSTER=${_STAGE}'
install-sops-on-cloudbuild.sh
#!/bin/bash -eu
SOPS_VERSION=3.3.0
curl -L https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux -o /usr/bin/sops \
&& chmod +x /usr/bin/sops
-
gcloud
コマンドで、GKEのkubernetesクラスタに対するcredentialを取得する- cloudbuildのサービスアカウントに権限をつけておく必要がある
-
kube-apply
は./build staging | kubectl apply -f -
のような事をやっているだけ - 環境毎にクラスタを分けているので、
substitutions
で_STAGE
を渡すようにしてデプロイ先を切り替える
$ gcloud builds submit --config=cloudbuild.yaml --substitutions=_STAGE=staging .
kustomizeへの移行でハマったポイント
selectorがimmutableなので変更できない
問題点
- commonLabelsに入れたラベルはDeploymentなどのselectorにも自動的に挿入される
- これによって、既存のselectorと差異が出ると、apiVersionが
apps/v1
でselectorがimmutableなリソースは変更ができなくてエラーになる
In API version apps/v1, a Deployment’s label selector is immutable after it gets created.
- transformer configでdisableできないか試したができなかった (2019/05時点)
解決策
-
apiVersion
をapps/v1beta1
など古いバージョンに戻すと変更できる- ※どのバージョンまで変更できるかまでは試してない
selectorを変更した事でゾンビpodが発生する
問題点と原因
- これもcommonLabelsなどで既存のpodのlabelとselectorに差異が発生することによって起こる
-
例
- 既存
- deployment
- spec.selector.matchLabels
- app: hoge
- spec.template.metadata.labels
- app: hoge
- spec.selector.matchLabels
- deployment
- 新規
- commonLabels
- stage: staging
- deployment
- spec.selector.matchLabels
- app: hoge
- spec.template.metadata.labels
- app: hoge
- spec.selector.matchLabels
- commonLabels
- こんな状態になると、新規の方はselectorとpodのlabelに
stage
というのが追加される - 前述の方法でselectorを変更すると、旧DeploymentのReplicaSetに紐付いているpodは新Deploymentのselectorと一致しないので、Deploymentが管理してくれなくなる
- そしてゾンビってしまう
- 既存
- https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#label-selector-updates
This change is a non-overlapping one, meaning that the new selector does not select ReplicaSets and Pods created with the old selector, resulting in orphaning all old ReplicaSets and creating a new ReplicaSet.
解決策
- ゾンビpodを手動で削除する
- いい方法あったら知りたかった…
感想
- configMapGeneratorとsecretGeneratorはまさに欲しかった機能だったのでありがたい
- kustomization.yaml以外は ほぼ kubernetesのmanifestそのものなので、覚えることは少ない
- kustomization.yamlの
images
が地味に便利- 環境毎にデプロイするイメージのタグを変えたいので
- kustomize/image.md
- まだまだ運用の知見が乏しいので、改善の余地が盛りだくさん