koukiblog

たぶんweb系の話題

HelmでCRDを利用するときの注意

HelmでCRDを扱う際にいくつか注意点があったので備忘のため残しておきます

helmで管理しているリソースのCRD、APIVersionが廃止されると、chart内のCRD,APIVersionをアップデートしてもhelm upgrade時にエラーになります。

たとえば、Kubernetes 1.16から下記のようなDeploymentのapps/v1beta1が削除されたり

apiVersion: apps/v1beta1
kind: Deployment

CertManagerのようなCRDを利用しているツールを導入している場合、バージョンアップに伴いCRDが廃止されたりします。CertManagerでは、1.7でv1beta1が廃止されています。

Release 1.7 - cert-manager Documentation

CRD, APIVersionの廃止までには移行期間があるので、この期間内にマニフェストのアップデートを行えば問題ありません。様々な事情でアップデートできないまま廃止されるとマニフェストを更新しても HelmによるUpgradeができなくなります。これはHelmのアーキテクチャによる制約です。

なぜ?

Helmはリソースの更新を行う場合、デプロイ済みのreleaseと、デプロイされる予定の次のreleaseの差分を確認して、それを反映しています。Helmは、Goのテンプレートを展開してkubectl applyしてるだけではないんです。

そのため、デプロイ済みのrelease内に廃止されたCRD, APIVersionが含まれているとそれがパースできなくなりエラーになります。

Helmのコード見ると、現在のリソースとデプロイ予定のリソースを比較しているコードが確認できます。 github.com

特にこのループでは、orignialにあってtargetに存在しないリソースを削除していて、これは kubectl apply とは異なる挙動です。 kubectl applyではリソースの削除は行われないため、ある程度の期間運用しているとリソース削除は問題になってきます(upgradeしていくだけでリソース削除できるのはHelmを利用するメリットの1つだと思います

   for _, info := range original.Difference(target) {
        c.Log("Deleting %s %q in namespace %s...", info.Mapping.GroupVersionKind.Kind, info.Name, info.Namespace)

        if err := info.Get(); err != nil {
            c.Log("Unable to get obj %q, err: %s", info.Name, err)
            continue
        }
        annotations, err := metadataAccessor.Annotations(info.Object)
        if err != nil {
            c.Log("Unable to get annotations on %q, err: %s", info.Name, err)
        }
        if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy {
            c.Log("Skipping delete of %q due to annotation [%s=%s]", info.Name, ResourcePolicyAnno, KeepPolicy)
            continue
        }
        if err := deleteResource(info); err != nil {
            c.Log("Failed to delete %q, err: %s", info.ObjectName(), err)
            continue
        }
        res.Deleted = append(res.Deleted, info)
    }

対応

上記のような状態になると、releaseを更新するしかありません。以前はreleaseを手元にダウンロードして編集するしか方法がなかったのですが、現在はHelmが便利なpluginを提供してくれています。

mapkubeapis というHelm Pluginを利用することで簡単にreleaseを更新することができます。

GitHub - helm/helm-mapkubeapis: This is a Helm plugin which map deprecated or removed Kubernetes APIs in a release to supported APIs

たとえば、cert-manager.io/v1alpha2 のCertificateリソースを、cert-manager.io/v1 に置き換えたい場合

下記のようなyamlを作成してmap.yamlとして保存します。(depreactedInVersion, remvedInVersionは置き換えが発生するように適当に設定すればok)

mappings:
   -  deprecatedAPI: "apiVersion: cert-manager.io/v1alpha2\nkind: Certificate"
      newAPI: "apiVersion: cert-manager.io/v1\nkind: Certificate"
      deprecatedInVersion: "v1.9"
      removedInVersion: "v1.16"

作成したmap.yamlを利用してmapkubeapis を実行することでreleaseを更新することが可能です。

helm mapkubeapis #{release_name} --mapfile map.yaml

まとめ

廃止される前にマニフェストをアップデートしていくのが一番よいのですが、様々な事情でこのHelmの制約にハマってしまう方もいると思うので役に立つとうれしいです。

なお、公式のドキュメントではこのトピックについて、専用のページが設けられていて、releaseを直接編集する方法も紹介されています。

helm.sh