この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
このブログを書くことになったきっかけです。
kubernetes で deployment リソースに変更を加えずに rolling update させる - Qiita
TL;DR
- PodTemplateSpec の annotation を付与/更新して実現してる
kubectl rollout restart とは
$ kubectl rollout restart -h
Restart a resource.
A deployment with the "RolloutStrategy" will be rolling restarted.
Examples:
# Restart a deployment
kubectl rollout restart deployment/nginx
resource の restart を実行するコマンドです。
(筆者の環境はサーバは v1.13.5 だったが使えた)
ここの記述を見て、v1.12.6 である Amazon EKS クラスタに対して試してみたら使えたのでソースを追ってみました。
ソースコード
v1.15.0-rc.1 の tag を使ってみていきます。
kubectl rollout restart
コマンドはここに定義されています。
Run: func(cmd *cobra.Command, args []string)
cmd := &cobra.Command{
Use: "restart RESOURCE",
DisableFlagsInUseLine: true,
Short: i18n.T("Restart a resource"),
Long: restartLong,
Example: restartExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.RunRestart())
},
ValidArgs: validArgs,
}
Run: func(cmd *cobra.Command, args []string)
の中がこのコマンドで実行される処理の中身です。
func (o *RestartOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error
cmdutil.CheckErr(o.Complete(f, cmd, args))
ここで呼ばれているのが
// Complete completes all the required options
func (o *RestartOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.Resources = args
o.Restarter = polymorphichelpers.ObjectRestarterFn
var err error
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
o.PrintFlags.NamePrintFlags.Operation = operation
return o.PrintFlags.ToPrinter()
}
o.Builder = f.NewBuilder
return nil
}
なんか色々やってますが、注目すべきはここ。
o.Restarter = polymorphichelpers.ObjectRestarterFn
Restarter に function らしき物を突っ込んでいます。 名前からして restart のロジックはこの function っぽいのが推測できますね。
Restarter の定義はこうです。
type RestartOptions struct {
Restarter polymorphichelpers.ObjectRestarterFunc
}
polymorphichelpers.ObjectRestarterFunc
は type として定義されています。
// ObjectRestarterFunc is a function type that updates an annotation in a deployment to restart it..
type ObjectRestarterFunc func(runtime.Object) ([]byte, error)
今度は Restarter に突っ込んでいる polymorphichelpers.ObjectRestarterFn
をみていきます。
定義されているのはここ。
var ObjectRestarterFn ObjectRestarterFunc = defaultObjectRestarter
defaultObjectRestarter
が実体らしいです。今度はこれをみていきます。
func defaultObjectRestarter(obj runtime.Object) ([]byte, error)
func defaultObjectRestarter(obj runtime.Object) ([]byte, error) {
switch obj := obj.(type) {
case *extensionsv1beta1.Deployment:
if obj.Spec.Paused {
return nil, errors.New("can't restart paused deployment (run rollout resume first)")
}
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion), obj)
// 略
}
Object を渡して switch で分岐しています。そして kubectl.kubernetes.io/restartedAt
という annotation を更新しているっぽいのがわかりますね。
ここまでくるとだいたい予想がついたのではないでしょうか。それでは、この function を実行している場所に移動します。
func (o RestartOptions) RunRestart() error
for _, patch := range set.CalculatePatches(infos, scheme.DefaultJSONEncoder(), set.PatchFn(o.Restarter)) {
info := patch.Info
if patch.Err != nil {
resourceString := info.Mapping.Resource.Resource
if len(info.Mapping.Resource.Group) > 0 {
resourceString = resourceString + "." + info.Mapping.Resource.Group
}
allErrs = append(allErrs, fmt.Errorf("error: %s %q %v", resourceString, info.Name, patch.Err))
continue
}
if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
allErrs = append(allErrs, fmt.Errorf("failed to create patch for %v: empty patch", info.Name))
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil)
if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch: %v", err))
continue
}
info.Refresh(obj, true)
printer, err := o.ToPrinter("restarted")
if err != nil {
allErrs = append(allErrs, err)
continue
}
if err = printer.PrintObj(info.Object, o.Out); err != nil {
allErrs = append(allErrs, err)
}
}
関連するところを見ていきます。
for _, patch := range set.CalculatePatches(infos, scheme.DefaultJSONEncoder(), set.PatchFn(o.Restarter)) {
ここでパッチの計算をしています。set.PatchFn(o.Restarter)
の部分で先ほど見た func defaultObjectRestarter(obj runtime.Object) ([]byte, error)
を渡しています。set.CalculatePatches()
の中で呼び出しが行われます。
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil)
ここで実際に Patch API を呼び出して Object の更新を行なっています。
annotation が書き換えられることで Deployment のローリングアップデートが行われていたという仕組みでした。
動かして確認
雑に kubectl run
で Deployment を作成してみます。
$ kubectl run nginx --image=nginx --replicas=1
YAML をみてみます。
$ kubectl get deployment.apps/nginx -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2019-06-17T15:51:10Z"
generation: 1
labels:
run: nginx
name: nginx
namespace: default
resourceVersion: "2441509"
selfLink: /apis/apps/v1/namespaces/default/deployments/nginx
uid: ba468f9e-9117-11e9-aa8e-06fb2bea1d76
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
run: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
run: nginx
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
availableReplicas: 1
conditions:
- lastTransitionTime: "2019-06-17T15:51:19Z"
lastUpdateTime: "2019-06-17T15:51:19Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: "2019-06-17T15:51:10Z"
lastUpdateTime: "2019-06-17T15:51:19Z"
message: ReplicaSet "nginx-dbddb74b8" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 1
readyReplicas: 1
replicas: 1
updatedReplicas: 1
次に kubectl rollout restart
を実行します。
$ kubectl rollout restart deployment.apps/nginx
deployment.apps/nginx restarted
その後、もう一度 kubectl get deployment.apps/nginx -o yaml
をすると diff が下記のようになりました。
--- foo.yaml 2019-06-18 00:53:14.000000000 +0900
+++ bar.yaml 2019-06-18 00:54:32.000000000 +0900
@@ -2,14 +2,14 @@
kind: Deployment
metadata:
annotations:
- deployment.kubernetes.io/revision: "1"
+ deployment.kubernetes.io/revision: "2"
creationTimestamp: "2019-06-17T15:51:10Z"
- generation: 1
+ generation: 2
labels:
run: nginx
name: nginx
namespace: default
- resourceVersion: "2441509"
+ resourceVersion: "2442002"
selfLink: /apis/apps/v1/namespaces/default/deployments/nginx
uid: ba468f9e-9117-11e9-aa8e-06fb2bea1d76
spec:
@@ -26,6 +26,8 @@
type: RollingUpdate
template:
metadata:
+ annotations:
+ kubectl.kubernetes.io/restartedAt: "2019-06-18T00:53:51+09:00"
creationTimestamp: null
labels:
run: nginx
@@ -52,12 +54,12 @@
status: "True"
type: Available
- lastTransitionTime: "2019-06-17T15:51:10Z"
- lastUpdateTime: "2019-06-17T15:51:19Z"
- message: ReplicaSet "nginx-dbddb74b8" has successfully progressed.
+ lastUpdateTime: "2019-06-17T15:53:56Z"
+ message: ReplicaSet "nginx-857965c444" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
- observedGeneration: 1
+ observedGeneration: 2
readyReplicas: 1
replicas: 1
updatedReplicas: 1
期待通りに kubectl.kubernetes.io/restartedAt: "2019-06-18T00:53:51+09:00"
という annotation が付与されました。
まとめ
kubectl rollout restart
コマンドによるローリングアップデートは kubectl.kubernetes.io/restartedAt
という annotation を PodTemplateSpec に付与/更新して実現している。