Kubernetesで同名の異なる内容のリソースを同時にapplyしたときの挙動について調べてみた
はじめに
Kubernetesではアプリケーションの実行や,そのアプリケーションを外部に公開するための設定の適用,Kubernetesクラスタ(以下クラスタと表記)内の権限管理など多くの操作を"リソース"と呼ばれるものを用いて行います.
例えば下のyaml形式のテキストはKubernetesにおけるアプリケーションの最小実行単位であるPodを定義したマニフェストです.
apiVersion: v1 kind: Pod metadata: labels: run: demo name: demo spec: containers: - image: nginx:latest name: nginx resources: {} dnsPolicy: ClusterFirst restartPolicy: Never
Kubernetesの使用者はこのようなマニフェストをAPIサーバに渡してあげることで様々な操作を行います.
さてこのマニフェストをAPIサーバに渡してあげる方法ですが,これにはいくつかの方法が用意されています.
そのうちおそらく誰もが使ったことがあるであろう方法がkubectl
と呼ばれるCLIツールを用いる方法です.
このkubectl
にはクラスタを操作するためのサブコマンドがいくつか用意されていますが,リソースの作成に関するコマンドの一つがkubectl apply
です.
このapplyコマンドでは作成対象のリソースがクラスタになければ新しく作成し,もしあった場合は変更部分を更新します.
実行すると以下のような結果が出力されます.
$ kubectl apply -f demo-pod.yaml pod/demo created # 使用イメージのタグを変更したものをapplyする $ kubectl apply -f demo-pod-changed.yaml pod/demo configured
ところでこのapplyコマンドを下のように同時に実行したらどうなるのでしょう?
# pod-changed.yamlはpod.yaml内の使用イメージタグのみを変更したもの $ kubectl apply -f pod.yaml & kubectl apply -f pod-changed.yaml
おそらく結果として考えられるの以下の3つです.
- どっちかがcreateされて,どっちかがconfiguredされる
- 同じ名前の違う内容のリソース(ここではPod)が複数作成される
- エラーで失敗する
さて結果はどうなるのでしょうか?
確認してみましょう!
kubectl applyを同時に2個実行してみた
実行結果を貼り付けます.
$ kubectl apply -f pod.yaml & ./kubectl apply -f pod-changed.yaml pod/demo created Error from server (AlreadyExists): error when creating "pod.yaml": pods "demo" already exists
どうやらpod.yaml
に記載されているPodリソースを作成時にAPIサーバ内でエラーが発生したようです.1
というわけで正解は3. エラーで失敗する
でした.
ところで先ほどapplyコマンドについて以下のような説明をしました.
このapplyコマンドでは作成対象のリソースがクラスタになければ新しく作成し,もしあった場合は変更部分を更新します.
にも関わらずAlready Exists
で失敗するのは何故でしょう?
すでに存在しているのがわかったなら更新してくれよという気持ちになる人も多いと思います.
少なくとも僕はそんな気持ちです.
どうしてこんなことになるのか,次は実際にコードを見てみることにしましょう.
kubectl apply のコードを見てみる
幸いKubernetesのコードはGitHubで誰でも見れるようになっているのでこれを見ていきます.
まずはkubectlコマンドのコードリーディングの入口を確認します.
kubernetes/cmd/kubectl/kubectl.go L35-L52
func main() {
rand.Seed(time.Now().UnixNano())
command := cmd.NewDefaultKubectlCommand()
NewDefaultKubectlCommand()を読み進めていくとapplyコマンドの本体が見えてきます.
この中にo.RUN()というapply処理の本体ぽいものが見られます.
kubernetes/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply.go L162-L206
Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd)) cmdutil.CheckErr(validateArgs(cmd, args)) cmdutil.CheckErr(validatePruneAll(o.Prune, o.All, o.Selector)) cmdutil.CheckErr(o.Run()) },
o.Run()
に飛ぶとコメントにRun executes the 'apply' command.
とありますね.
どうやら当たりのようです.
kubernetes/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply.go L355-L403
// Run executes the `apply` command. func (o *ApplyOptions) Run() error {
引き続き読み進めているとapplyOneObject()
内でinfo.Get()
を呼び出しており,作成リソースがすでに存在しないかどうかの判定を行っているのがわかります.
kubernetes/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply.go
if err := info.Get(); err != nil { if !errors.IsNotFound(err) { return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err) }
そしてリソースがなかった場合はkubernetes/staging/src/k8s.io/cli-runtime/pkg/resource/helper.goのCreate()
以降でリソースの作成処理が行われ,あった場合はkubernetes/staging/src/k8s.io/kubectl/pkg/cmd/apply/patcher.go L183-L205のPatch()
以降でリソースの更新処理が行われています.2
ところでこのCreate()
関数ですがkubectl create
のコマンドでも使われているようです.
つまりapplyコマンドはinfo.Get()
で同名リソースが取得できなかった場合,createコマンドと同様の動作をするようです.
createコマンドはapplyコマンド同様にリソース作成に用いられるコマンドですが,applyコマンドと違いリソースの更新作業は行いません.すでに存在するリソースを作成しようとするとエラー文を表示します.
(ここでそういえばAlready Existsのエラーメッセージはcreateコマンドのときに見たことあるなあと思い出すなど...)
$ kubectl create -f pod.yaml pod/demo created $ kubectl create -f pod-changed.yaml Error from server (AlreadyExists): error when creating "pod-changed.yaml": pods "demo" already exists
というわけでapplyコマンドを同時実行した際にエラーが発生したのは「info.Get()
実行時には同名リソースが存在しなかったが,その後のリソースのcreate処理中に同時実行されているもう片方で同名リソースが作成されていた」,というのが原因だということがわかりました.
めでたしめでたし.
さいごに
いかがでしたでしょうか?
実際にコードを読みながら動作を把握するのは面白いですね.
今回はAPIサーバ内の動作については深追いしませんでしたが,ここも見てみると面白いことがわかるかもしれません.
明日から使える知識...というほどの知見ではありませんが,この記事が誰かのクベ活に役立てれば幸いです.
というわけで本記事はあくあたん工房 Advent Calendar 2020 21日目の記事でした.
明日はkobaくんが何か書いてれるようです.お楽しみに〜