36
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

Organization

GKE におけるローリングアップデート

はじめに

GKE では 1 台以上の Container をグルーピングする要素として Pod があります。その Pod のレプリカを管理する要素として Replication Controller (RC) があります。また、各 Pod に対して負荷分散を行う要素として Service があります。

実際のリクエストは Service を経由して各 Pod に負荷分散されます。それぞれの Pod はメタデータとしてラベル付けを行い、Service では負荷分散の対象とする Pod のラベルを指定します。

RC には、管理下の Pod を新しいイメージに1台ずつローリングでアップデートする機能があります。これはリリース作業で使われる機能ですが、対象とする RC が管理している各 Pod の Container 数によってローリングアップデートの方法が異なります。特に Multi Container Pod に対するローリングアップデートが難解なので、忘却メモとして投稿しました。

環境

現時点でのバージョンは GKE (Kubernetes) v1.1 です。

Single Container

api-pods ラベルがつけられた Pod を selector として持つ RC と Service がある構成を例に挙げて紹介します。実際の Spec ファイルを下記に示します。

Space files

API Replication Controller Spec

{
   "kind": "ReplicationController",
   "apiVersion": "v1",
   "metadata": {
      "name": "api-rc",
      "labels": {
         "name": "api-rc"
      }
   },
   "spec": {
      "replicas": 3,
      "selector": {
         "name": "api-pods"
      },
      "template": {
         "metadata": {
            "labels": {
               "name": "api-pods"
            }
         },
         "spec": {
            "containers": [
               {
                  "name": "node-api-container",
                  "image": "asia.gcr.io/xxxxx/node-api:1.0.0",
                  "ports": [
                     {
                        "containerPort":3000,
                        "protocol":"TCP"
                     }
                  ]
               }
            ]
         }
      }
   }
}

API Service Spec

{
   "kind": "Service",
   "apiVersion": "v1",
   "metadata": {
      "name": "api-srv",
      "labels": {
         "name": "api-srv"
      }
   },
   "spec": {
      "type": "LoadBalancer",
      "ports": [
        {
          "port": 3000,
          "targetPort": 3000,
          "protocol": "TCP"
        }
      ],
      "selector": {
         "name": "api-pods"
      }
   }
}

Rolling Update

このような構成において api-rc が管理している各 Pod のイメージを node-api:1.0.1 にアップデートします。

$ kubectl rolling-update api-rc --image=asia.gcr.io/xxxxx/node-api:1.0.1
Created api-rc-f9eed97d434b09b484ff9448ad82064b
Scaling up api-rc-f9eed97d434b09b484ff9448ad82064b from 0 to 3, scaling down api-rc from 3 to 0 (keep 3 pods available, don't exceed 4 pods)
Scaling api-rc-f9eed97d434b09b484ff9448ad82064b up to 1
Scaling api-rc down to 2
Scaling api-rc-f9eed97d434b09b484ff9448ad82064b up to 2
Scaling api-rc down to 1
Scaling api-rc-f9eed97d434b09b484ff9448ad82064b up to 3
Scaling api-rc down to 0
Update succeeded. Deleting old controller: api-rc
Renaming api-rc-f9eed97d434b09b484ff9448ad82064b to api-rc
replicationcontroller "api-rc" rolling updated

新しい RC が api-rc-f9eed97d434b09b484ff9448ad82064b として生成され、それぞれの Pod を一台ずつ increment / decrement し、正常に終わったタイミングで RC 名の変更が行われました。アップデート処理中においても各 Pod のラベルは api-pods として付けられているため Service からの負荷分散は停止することなくリリースできるようになっています。ただし、処理中に新旧が混在する状況となるため、あるユーザーがそれぞれのバージョンに接続する可能性があることには注意が必要です。

Multi Container Pod

こっから本題。Pod は複数の Container で構成することができます。例えば node-api-containerfluentd-container をグルーピングした Pod を構成すると node-api-container から localhost:24224fluentd-container と疎通できます。実際の Spec ファイルを下記に示します。

Spec files

API Replication Controller Spec

{
   "kind": "ReplicationController",
   "apiVersion": "v1",
   "metadata": {
      "name": "api-rc",
      "labels": {
         "name": "api-rc"
      }
   },
   "spec": {
      "replicas": 3,
      "selector": {
         "name": "api-pods"
      },
      "template": {
         "metadata": {
            "labels": {
               "name": "api-pods"
            }
         },
         "spec": {
            "containers": [
               {
                  "name": "node-api-container",
                  "image": "asia.gcr.io/xxxxx/node-api:1.0.0",
                  "ports": [
                     {
                        "containerPort":3000,
                        "protocol":"TCP"
                     }
                  ]
               },
               {
                  "name": "fluentd-container",
                  "image": "asia.gcr.io/xxxxx/fluentd:1.0.0",
                  "ports": [
                     {
                        "containerPort":24224,
                        "protocol":"TCP"
                     }
                  ]
               }
            ]
         }
      }
   }
}

API Service Spec

{
   "kind": "Service",
   "apiVersion": "v1",
   "metadata": {
      "name": "api-srv",
      "labels": {
         "name": "api-srv"
      }
   },
   "spec": {
      "type": "LoadBalancer",
      "ports": [
        {
          "port": 3000,
          "targetPort": 3000,
          "protocol": "TCP"
        }
      ],
      "selector": {
         "name": "api-pods"
      }
   }
}

Rolling Update

このような Multi Container Pod を管理している RC に対するローリングアップデートは Single Container Pod で使用したイメージファイルを指定した方法は利用できません。Multi Container Pod に対しては新しい RC の Spec ファイルを指定する必要があります。今回は api-rc が管理している各 Pod のイメージのうち node-api-containernode-api:1.0.1 にアップデートします。

$ cat << EOS | kubectl rolling-update api-rc -f -
{
   "kind": "ReplicationController",
   "apiVersion": "v1",
   "metadata": {
      "name": "api-rc",
      "labels": {
         "name": "api-rc"
      }
   },
   "spec": {
      "replicas": 3,
      "selector": {
         "name": "api-pods"
      },
      "template": {
         "metadata": {
            "labels": {
               "name": "api-pods"
            }
         },
         "spec": {
            "containers": [
               {
                  "name": "node-api-container",
                  "image": "asia.gcr.io/xxxxx/node-api:1.0.1",
                  "ports": [
                     {
                        "containerPort":3000,
                        "protocol":"TCP"
                     }
                  ]
               },
               {
                  "name": "fluentd-container",
                  "image": "asia.gcr.io/xxxxx/fluentd:1.0.0",
                  "ports": [
                     {
                        "containerPort":24224,
                        "protocol":"TCP"
                     }
                  ]
               }
            ]
         }
      }
   }
}
EOS
error: - cannot have the same name as the existing ReplicationController api-rc
see 'kubectl rolling-update -h' for help.

既に同じ名前の RC が存在すると怒られました。Single Container Pod の場合は良い感じにリネーム処理までやってくれましたが Multi Container Pod の場合はダメなようです。実際の selector で使用している Pod の name ラベルが同じであれば Service からの負荷分散は停止することなくアップデートできるため、新しい RC の名前を api-rc-v2 に変更してもう一度実行します。

$ cat << EOS | kubectl rolling-update api-rc -f -
{
   "kind": "ReplicationController",
   "apiVersion": "v1",
   "metadata": {
      "name": "api-rc-v2",
      "labels": {
         "name": "api-rc-v2"
      }
   },
   "spec": {
      "replicas": 3,
      "selector": {
         "name": "api-pods"
      },
      "template": {
         "metadata": {
            "labels": {
               "name": "api-pods"
            }
         },
         "spec": {
            "containers": [
               {
                  "name": "node-api-container",
                  "image": "asia.gcr.io/xxxxx/node-api:1.0.1",
                  "ports": [
                     {
                        "containerPort":3000,
                        "protocol":"TCP"
                     }
                  ]
               },
               {
                  "name": "fluentd-container",
                  "image": "asia.gcr.io/xxxxx/fluentd:1.0.0",
                  "ports": [
                     {
                        "containerPort":24224,
                        "protocol":"TCP"
                     }
                  ]
               }
            ]
         }
      }
   }
}
EOS
error: - must specify a matching key with non-equal value in Selector for api-rc
see 'kubectl rolling-update -h' for help.

別のエラーが出てきました。そして、いまいち意味が分からない。既存の RC の selector の値が non-equalmatching key ... ? こんな時はソースで理解したほうが確実なので読んでみます。https://github.com/kubernetes/kubernetes/blob/v1.1.4/pkg/kubectl/cmd/rollingupdate.go##L248-L261

    // To successfully pull off a rolling update the new and old rc have to differ
    // by at least one selector. Every new pod should have the selector and every
    // old pod should not have the selector.
    var hasLabel bool
    for key, oldValue := range oldRc.Spec.Selector {
        if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue {
            hasLabel = true
            break
        }
    }
    if !hasLabel {
        return cmdutil.UsageError(cmd, "%s must specify a matching key with non-equal value in Selector for %s",
            filename, oldName)
    }

「旧 RC の selector で使用している key が、新 RC の selector の key として定義されていて、かつ、その key の値が異なっている」パターンがひとつもない場合に生じるエラーのようです・・・って日本語で書いても難解なのでソース読んでくださいw

現状の Spec ファイルだと各 Pod には name ラベルのみを定義しています。このエラーを回避するためには、新しい RC で selector の name ラベルの値を変更する必要がありますが、同様に各 Pod の name ラベルの値も変更する必要があります。しかしながら、既存の Service でも selector を name ラベルで指定しているため、負荷分散が停止します。

Multi Container Pod を使用する場合は RC を作成する段階でローリングアップデートを考慮する必要があることが分かりました。様々な方法がありますが、今回は作成時の UnixTime を RC 名の語尾に追加し、各 Pod に version ラベルとして追加します。実際の Spec ファイルを下記に示します。

Spec files [fixed]

API Replication Controller Spec

{
   "kind": "ReplicationController",
   "apiVersion": "v1",
   "metadata": {
      "name": "api-rc-1453323827",
      "labels": {
         "name": "api-rc-1453323827"
      }
   },
   "spec": {
      "replicas": 3,
      "selector": {
         "name": "api-pods",
         "version": "1453323827"
      },
      "template": {
         "metadata": {
            "labels": {
               "name": "api-pods",
               "version": "1453323827"
            }
         },
         "spec": {
            "containers": [
               {
                  "name": "node-api-container",
                  "image": "asia.gcr.io/xxxxx/node-api:1.0.0",
                  "ports": [
                     {
                        "containerPort":3000,
                        "protocol":"TCP"
                     }
                  ]
               },
               {
                  "name": "fluentd-container",
                  "image": "asia.gcr.io/xxxxx/fluentd:1.0.0",
                  "ports": [
                     {
                        "containerPort":24224,
                        "protocol":"TCP"
                     }
                  ]
               }
            ]
         }
      }
   }
}

API Service Spec

{
   "kind": "Service",
   "apiVersion": "v1",
   "metadata": {
      "name": "api-srv",
      "labels": {
         "name": "api-srv"
      }
   },
   "spec": {
      "type": "LoadBalancer",
      "ports": [
        {
          "port": 3000,
          "targetPort": 3000,
          "protocol": "TCP"
        }
      ],
      "selector": {
         "name": "api-pods"
      }
   }
}

Rolling Update

準備が整ったため、実際にローリングアップデートを行います。以前と同様に api-rc が管理している各 Pod のイメージのうち node-api-containernode-api:1.0.1 にアップデートします。今回は既存の RC 名が推測できないため kubectl コマンドを実行して既存の RC 名を取得する処理も行なっています。

$ CURRENT_RC_NAME=$(kubectl get rc | grep '^api-rc' | cut -d ' ' -f 1)
$ CURRENT_UNIXTIME=$(Date +'%s')
$ cat << EOS | kubectl rolling-update ${CURRENT_RC_NAME} -f -
{
   "kind": "ReplicationController",
   "apiVersion": "v1",
   "metadata": {
      "name": "api-rc-${CURRENT_UNIXTIME}",
      "labels": {
         "name": "api-rc-${CURRENT_UNIXTIME}"
      }
   },
   "spec": {
      "replicas": 3,
      "selector": {
         "name": "api-pods"
         "version": "${CURRENT_UNIXTIME}"
      },
      "template": {
         "metadata": {
            "labels": {
               "name": "api-pods"
               "version": "${CURRENT_UNIXTIME}"
            }
         },
         "spec": {
            "containers": [
               {
                  "name": "node-api-container",
                  "image": "asia.gcr.io/xxxxx/node-api:1.0.1",
                  "ports": [
                     {
                        "containerPort":3000,
                        "protocol":"TCP"
                     }
                  ]
               },
               {
                  "name": "fluentd-container",
                  "image": "asia.gcr.io/xxxxx/fluentd:1.0.0",
                  "ports": [
                     {
                        "containerPort":24224,
                        "protocol":"TCP"
                     }
                  ]
               }
            ]
         }
      }
   }
}
EOS
Created api-rc-1453400999
Scaling up api-rc-1453400999 from 0 to 3, scaling down api-rc-1453323827 from 3 to 0 (keep 3 pods available, don't exceed 4 pods)
Scaling api-rc-1453400999 up to 1
Scaling api-rc-1453323827 down to 2
Scaling api-rc-1453400999 up to 2
Scaling api-rc-1453323827 down to 0
Scaling api-rc-1453400999 up to 3
Update succeeded. Deleting api-rc-1453323827
replicationcontroller "api-rc-1453323827" rolling updated to "api-rc-1453400999"

正常に完了しました。Single Container Pod のように同じ RC 名でのローリングアップデートはできなくなったので複雑になりましたが、既存 Service の selector は name ラベルのみを指定しているため負荷分散を停止することなくリリースできるようになっています。実際に既存の Service の :3000 に対してリクエストを送信すると正常に動作しました。複雑だったけど、もっと良い方法がありましたらご指摘いただければ幸いです。

まとめ

GKE におけるローリングアップデートは Single Container Pod の場合は凄くシンプル。一方で Multi Container Pod の場合は Spec ファイルを指定する必要があり、新旧 RC で値を変更できるバージョンのようなラベルを付与する必要があります。

おわりに

この内容を gcp ja night #31 で軽く触れましたが、補足版として解決するまでの流れをそのまま書いてみました。発表資料も併せてご覧ください!

http://gcpja.connpass.com/event/23874/
http://www.slideshare.net/katsutoshinagaoka/gke-57322091

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
36
Help us understand the problem. What are the problem?