Help us understand the problem. What is going on with this article?

Kubernetesのmanifest管理をkustomizeに移行した話

今までkubernetesのmanifestはテンプレートエンジンを使って環境毎にビルドして使っていましたが
kubectl1.14にkustomizeが統合された(?)のもあり
kustomizeでビルドする感じに移行したので、やったことをメモ

Kubernetes 1.14: Production-level support for Windows Nodes, Kubectl Updates, Persistent Local Volumes GA - Kubernetes

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
  • 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 で必要な部分だけ書き換える
    • リソースの種類毎に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

このあたりモヤモヤするのでベストプラクティスが知りたい、、、

Build

#!/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入りのイメージを使う
  • production
    • gitのpushをトリガーにしてCloudBuildで実行
    • 条件
      • branch: master
      • base/**/* or overlays/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 builds submit --config=cloudbuild.yaml --substitutions=_STAGE=staging .

kustomizeへの移行でハマったポイント

selectorがimmutableなので変更できない

問題点

In API version apps/v1, a Deployment’s label selector is immutable after it gets created.

解決策

  • apiVersionapps/v1beta1 など古いバージョンに戻すと変更できる
    • ※どのバージョンまで変更できるかまでは試してない

selectorを変更した事でゾンビpodが発生する

問題点と原因

  • これもcommonLabelsなどで既存のpodのlabelとselectorに差異が発生することによって起こる
    • 既存
      • deployment
        • spec.selector.matchLabels
          • app: hoge
        • spec.template.metadata.labels
          • app: hoge
    • 新規
      • commonLabels
        • stage: staging
      • deployment
        • spec.selector.matchLabels
          • app: hoge
        • spec.template.metadata.labels
          • app: hoge
    • こんな状態になると、新規の方は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
  • まだまだ運用の知見が乏しいので、改善の余地が盛りだくさん
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした