: O. Yuanying

kubectl apply --server-side に移行したい

Server-Side Apply への移行の問題点

kubectl apply時に、kubernetes 1.23 時点では client-side applyがデフォルトになっている。

これを sever-side apply に移行したい場合、単に、kubectl applyからkubectl apply --server-side、のように、 --server-sideフラグをつけるよう運用方法を変更した場合、問題が発生する。

簡単には以下のように問題が再現する。

問題の再現

以下の再現方法は、「Server-side apply: migration from client-side apply leaves stuck fields in the object #99003」に書かれている方法からの引用です。

まずは、通常の client-side applyでConfigMapを作ります。

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: test
data:
  key: value
  legacy: unused
EOF

次に、同じマニフェストをserver-side applyします。

cat <<EOF | kubectl apply --server-side -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: test
data:
  key: value
  legacy: unused
EOF

apiserverには適用したマニフェストが登録されていることがわかる。

$ kubectl get configmap -o yaml test | egrep -A 3 '^data:'
data:
  key: value
  legacy: unused
kind: ConfigMap

その後、legacy データを削除したマニフェストをserver-side applyで登録すると、

cat <<EOF | kubectl apply --server-side -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: test
data:
  key: value
EOF

legacy データを削除したにも関わらず、apiserver には legacy データが残ってしまっていることが確認できる。

$ kubectl get configmap -o yaml test | egrep -A 3 '^data:'
data:
  key: value
  legacy: unused
kind: ConfigMap

何が問題か?

つまり、消したはずのフィールドが残ってしまう。

自分の経験だと、消したはずの環境変数 env の値が残ってしまったせいで、予期しない挙動が起こって障害を起こしてしまった。

なぜフィールドが消えないか?

詳しくは「わかる!metadata.managedFields」を読もう!

対応策

問題は、manager: kubectl-client-side-apply になっている .metadata.managedFields である。とりあえずはこれを消す必要がある。fluxcdのissue では以下のワークアラウンドが紹介されている。

kubectl patch cm test --type=json -p='[{"op": "remove", "path": "/metadata/managedFields/1"}]'

ただ、この方法は、/metadata/managedFields/1manager: kubectl-client-side-apply であることを確認しているわけでは無いため、意図しないフィールドを消してしまう可能性がある。

例えば、以下のスクリプトでfield managerを確認してみると、

$ kubectl get configmap -o json --show-managed-fields | \
   jq -r ".items[] | select(.metadata.managedFields != null) |
     ( .metadata.managedFields | to_entries ) as \$managers | {
       name: .metadata.name,
       managers: [( \$managers[] |
         { key: .key, manager: .value.manager, operation: .value.operation }
       )]
     } |
     select(.managers != []) |
     \"## \" + .name  + \"\n\" +
     reduce .managers[] as \$m (\"\"; . + \"- \" + (\$m.key|tostring) + \" \"+ \$m.manager + \": \" + \$m.operation + \"\n\")
   "
## config
- 0 kubectl-client-side-apply: Update
- 1 kubectl-edit: Update

## kube-root-ca.crt
- 0 kube-controller-manager: Update

## nginx
- 0 kubectl-client-side-apply: Update

## test
- 0 kubectl: Apply
- 1 kubectl-client-side-apply: Update
- 2 kubectl-edit: Update
- 3 kubectl-replace: Update

1 番目に必ずしも manager: kubectl-client-side-apply がいないことがわかる。

また、他にも手動オペレーションで kubectl editkubectl replace などをおこなっていた場合は、それらの manager も server-side apply時のコンフリクトの原因となりうることがわかる。ということは、必要なのは kubectl-* 系のmanagerとkubectl: Update になっているmanagedFieldsの削除、ということになる。

つまり、必要なのはこれ。

RESOURCE_NAME=configmap
kubectl get ${RESOURCE_NAME} -o json --show-managed-fields | \
    jq -r ".items[] | select(.metadata.managedFields != null) |
    ( .metadata.managedFields | to_entries ) as \$managers | { 
    name: .metadata.name,
    managers: [( \$managers[] |
        { key: .key, manager: .value.manager, operation: .value.operation } |
        select((.manager | startswith(\"kubectl\")) or .manager == \"before-first-apply\") |
        select(.manager != \"kubectl\" and .operation != \"Apply\")
    )] | sort_by(.key) | reverse
    } |
    select(.managers != []) |
    {name: .name, items: [{op: \"remove\", path: (\"/metadata/managedFields/\" + (.managers[].key|tostring))}]} |
    \"kubectl patch ${RESOURCE_NAME} \" + .name + \" --type=json -p='\" + (.items|tostring) + \"';\"
"

注意点

注意点1

注意点としては、上記のパッチは client-side applyからserver-side apply時に何の変更もなく移行した場合にのみ有効ということである。

すでに owner が kubectl-client-side-apply のみになったフィールドが残ってしまっていた場合は、単に orphan なフィールドができてしまうだけなので、パッチを適用前にすでに想定外のフィールドが適用するオブジェクトに残ってしまっていないか確認しておく必要がある。

注意点2

server-side applyに完全に移行したとしても、運用時の手動オペレーション、例えば kubectl editkubectl replaceで意図しないフィールドマネージャーの変更が起こってしまう。

kubectl editなどの手動オペレーションを行なった場合は随時上記のパッチを利用してフィールドマネージャーをクリーンナップしておく必要がありそう。

まとめ

FluxCD のコードを見てみたところ、このようなCDツールを使っていた場合は特にユーザはこういったclient-side applyからserver-side applyへの移行に注意する必要がないように見える。

上記のPRでは、FluxCDの場合は今までのclient-side applyのフィールドマネージャーを一括削除し、server-side applyのフィールドマネージャーに移行している。

それ以外の運用者で、kubernetesにネイティブに対応していないCDツールで素のkubectl applyを利用していた場合、client-side applyからserver-side applyへの移行は注意したほうがよさそう。

(というか、もっと素直な移行ツールがあってよさそうなものだが、何か見逃している気がする。)