skaffold で image 作成時に volumes で クレデンシャルを渡そうとしてハマった

Docker image を作成する時プライベートなリポジトリをpullする、s3からなんか引っ張ってくる、などなど Dockerfile 内で認証情報などのクレデンシャルを利用した処理を行いたいことが多々あると思います。


僕はKubernetesのアプリケーションを開発する際に skaffoldというツールを利用しています。
image のビルドもskaffold と Kubernetes クラスタ内で image をビルドするため kaniko というツールを組み合わせて行なっています。

skaffold + kaniko で前述のようにクレデンシャルを利用する処理を含んだ Dockerfile からイメージを作成しようとしてハマった事を記録します。


今回は GitHub Packages に公開された npm package を取得する例を元に説明します。
npm package を取得するための認証は以下の内容を `.npmrc` ファイルとして保存する事で可能です。

docs.github.com

//npm.pkg.github.com/:_authToken=TOKEN


kaniko を利用してimageをビルドする場合、Kubernetes クラスタ上に kaniko pod が作成され pod上でビルドを行います。
ということで kaniko pod 作成時に 上記の .npmrc を 渡してあげると良さそうです。

僕は .npmrc を secret に保存し Volumes でマウントさせる方法を試しました。

skaffold を利用する場合、諸々の設定を skaffold.yaml に書きます。

skaffold.yaml のリファレンスは以下です。

skaffold.yaml | Skaffold

リファレンスを参考に以下のような skaffold.yaml を用意しました。

apiVersion: skaffold/v2beta10
kind: Config
build:
  artifacts:
  - image: gcr.io/k8s-skaffold/example
    context: .
    kaniko:
      volumeMounts:
      - name: npmrc
        mountPath: "/root/"
  cluster:
    pullSecretName: kaniko-secret
    volumes:
      - name: npmrc
        secret:
          secretName: npmrc

npmrc という名前のた secret を kaniko pod の /root/ にマウントしています。
secret は以下のような感じなので kaniko-pod に /root/.npmrc がマウントされます。

Name:        npmrc
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
.npmrc:  74 bytes


準備ができたので image をビルドしてみます

 $ skaffold build
parsing skaffold config: unable to parse config: yaml: unmarshal errors:
  line 8: field mountPath not found in type v1.VolumeMount
  line 17: field secret not found in type v1.Volume

はい。エラーが出ちゃいました。

  • mountPath プロパティが v1.VolumeMount 型に定義されていない
  • secret プロパティが v1.Volume に定義されていない

ということらしいです。ほんまかいな

まずは mountPath から確認です。
skaffold.yamlスキーマの定義は以下にあります。
skaffold/config.go at master · GoogleContainerTools/skaffold · GitHub

どうやら v1.VolumeMount とは k8s.io/api/core/v1 package のに定義されている型のようです。
定義を確認しに行くと mountPath プロパティはちゃんと v1.VolumeMount に定義されています。

はて、skaffold のバグかなと思って issue を漁ったらそれっぽいやつを見つけました
volume structures (mountPath, persistentVolumeClaim) must be lower-case to avoid unmarshal error · Issue #4175 · GoogleContainerTools/skaffold · GitHub

どうやら キー名を lower-case にしないとうまくパースできないバグが含まれていたようです。
修正の PullRequest はマージされていましたが、まだリリースされいないようなだったのでとりあえず lower-case に修正して試します。

apiVersion: skaffold/v2beta10
kind: Config
build:
  artifacts:
  - image: gcr.io/k8s-skaffold/example
    context: .
    kaniko:
      volumeMounts:
      - name: npmrc
        mountpath: "/root/"
  cluster:
    pullSecretName: kaniko-secret
    volumes:
      - name: npmrc
        secret:
          secretName: npmrc
 $ skaffold build
parsing skaffold config: unable to parse config: yaml: unmarshal errors:
  line 17: field secret not found in type v1.Volume

はい。エラーが 1つ解消されました。
続いて 「secret プロパティが v1.Volume に定義されていない」をみてみます。

type Volume struct {
	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
	VolumeSource `json:",inline" protobuf:"bytes,2,opt,name=volumeSource"`
}

github.com

v1.Volume は Name と VolumeSource のプロパティがあって VolumeSource はインラインに展開されます。
VolumeSource の方には secret プロパティが含まれているのでこちらも問題なさそうに見えます。

skaffold の方の定義は以下のようになっています (関係ある箇所抜粋)
skaffold/config.go at master · GoogleContainerTools/skaffold · GitHub

type ClusterDetails struct {
	// Volumes defines container mounts for ConfigMap and Secret resources.
	Volumes []v1.Volume `yaml:"volumes,omitempty"`
}

k8s.io/api/core/v1 の定義と skaffold の方の定義を見比べると skaffold の方は yaml タグを利用しているのに対し k8s.io/api/core/v1 は json タグを利用しています。
それが悪さをして VolumeSource をうまくインライン展開できていないようでした。
こちらのバグも先ほどの PullRequest で修正されているようです。


VolumeSource がインライン展開できていないだけなので以下のように yaml を修正するとエラーを回避することができます

apiVersion: skaffold/v2beta10
kind: Config
build:
  artifacts:
  - image: gcr.io/k8s-skaffold/example
    context: .
    kaniko:
      volumeMounts:
      - name: npmrc
        mountpath: "/root/"
  cluster:
    pullSecretName: kaniko-secret
    volumes:
      - name: npmrc
        volumesource:
          secret:
            secretname: npmrc

volumesource や secretname も例によって lower-case になっていることに注意してください。
エラーは回避できたとはいえ、修正の PullRequest がリリースされたら、上記の yaml は逆にパースエラーになりそうな気もするので早くリリースされるのを待つばかりです。