docker
GoogleCloudPlatform
gcp
kubernetes
GKE

Kubernetesを用いてGoogle Container Engineでコンテナクラスタをデプロイする〜入門編〜

More than 1 year has passed since last update.

はじめに

Google Cloud Next'17 in Tokyoでのセッション動画リストがようやく公開されました。

Google Container Engine(GKE)上でコンテナクラスタを本番運用する事例は日本でまだまだ少ないということもあり、公開されたセッション動画は非常に有用な情報源となっています。

このエントリでは、Kubernetesを用いてGKE上でコンテナクラスタを構築し、ローカルで開発したアプリケーションをデプロイする手順をまとめます。

Kubernetesとは

KubernetesはDockerコンテナのオーケストレーションおよび管理を行うためのツールです。元々はGoogleが内部でコンテナ運用するために作られたBorgというツールがありましたが、昨今のコンテナブームに後押しされてKubernetesとしてオープンソース化されています。

複数のDockerコンテナを管理するツールとしてはdocker-composeがありますが、コンテナクラスタの制御まではできず、本番環境で運用するのは非常に困難です。

KubernetesはGoogle製のツールということで、Google Container Engineと簡単に連携することができ、kubectlを用いてコンテナクラスタの構築・運用をコマンドで実行することができます。

設定ファイルもyaml形式で記載することができ、設定ファイル自体をソース管理して運用することが可能です。Dockerコンテナを自動ビルドしてくれるトリガー機能など、コンテナクラスタを運用するのに便利な機能がGCPで提供されており、低コストでコンテナクラスタを運用するのに最適な環境になっています。

Kubernetes用語の整理

まずはじめに、Kubernetesでの用語を整理します。

  • Pod
    • 複数のコンテナとディスクのまとまりをPodと呼びます。Kubernetesでデプロイする最小単位です。
    • 1つのコンテナだけをPodにすれば、Dockerコンテナと同じ感覚で使うことができます。
  • Node
    • Podを動作させるマシンのことです。GCPではGoogle Compute Engine(GCE)上のVMとして動作します。
    • 複数のNodeを組み合わせて、コンテナクラスタを構築します。NodeとPod全体を管理するマスターNodeはGKE上で自動的に構築されます。
    • Nodeのリソース状況を見て、PodをどのNodeに配置するかなどの面倒な運用はマスターNodeがやってくれるため、運用担当者が監視して逐一設定するというような手間がかかりません。
  • Service
    • Podにアクセスするためのポリシーをルール化する仕組みです。
      • 例えば、Webサーバー用のPodにはポート80、MySQLサーバーにはポート3306にアクセスするなど。
    • ロードバランシング設定もできるため、Webサーバ用途のPodを外部に公開することもSeviceだけで可能です。
      • ただし、規模の大きなロードバランシングにはIngressという機能を使うのがベターです。
  • Deployment
    • その名の通り、複数のPodをデプロイしたりロールバックしたりするなどの仕組みです。

デモについて

Kubernetesの動作確認をするためのデモアプリケーションをGithubリポジトリで準備しました。

https://github.com/yusukixs/kubernetes_demo
デモの流れは以下のとおりです。

  1. ローカル環境のDockerコンテナでアプリケーションの動作確認
  2. Google Container Clusterの作成
  3. ローカルでビルドしたDockerイメージをContainer RegistryにPUSH
  4. Container RegistryからPodにデプロイ
  5. Podのスケールアップ
  6. 修正したソースコードを反映したDockerイメージのビルドとPUSH
  7. Rolling UpdateによるPodの差し替え
  8. Podのロールバック

ローカル環境のDockerコンテナでアプリケーションの動作確認

ローカルでdocker build

まずはローカル環境でデモ用のNode.jsアプリケーションを動作させてみましょう。

$ git clone https://github.com/yusukixs/kubernetes_demo.git
$ cd kubernetes_demo
$ npm install
$ node server.js

ソース自体は「Hello World!」を表示するだけの簡単なものです。
ブラウザで http://localhost:3000/ にアクセスすると、Hello World! が表示されれば成功です。

server.js
var express = require('express');
var app = express();

app.set('view engine', 'jade');
app.set('views', __dirname + '/views');

app.get('/', function (req, res) {
    res.render('index', {
        title: 'Kubernetes Demo',
        message: 'Hello World!'
    });
});

app.listen(3000, function () {
    console.log('listening');
});

次にこのアプリケーション用のDockerイメージをビルドしてコンテナを実行します。

$ docker build -t kubernetes_demo:1.0 .
$ docker run -p 3000:3000 -d -it --rm --name nodejs kubernetes_demo:1.0
$ open http://localhost:3000/

同様にブラウザで http://localhost:3000/ にアクセスして、Hello World! が表示されることを確認してください。

Google Container Clusterの作成からコンテナデプロイまで

GCloud SDKのセットアップ

Google Cloud Platformの操作にはGCloud SDKを利用します。下記ページを参考にGCloud SDKをインストールしてください。
https://cloud.google.com/sdk/downloads?hl=ja

あらかじめ、Google Cloud Platformでプロジェクトを作成しておき、GCloudをセットアップします。

$ gcloud init

ダイアログ形式で設定を入力します。GCP用のGoogleアカウントでログインし、GKEを利用するGCPプロジェクトを選択します。

kubectlインストール

Kubernetesを操作するため、kubectlをインストールします。

# インストール済みコンポーネントのアップデート
$ gcloud components update
# kubectlのインストール
$ gcloud components update kubectl

Google Container Engineにクラスタ作成

Google Container Engine上にコンテナクラスタを作成します。

# nodejs-cluster という名称のGKEクラスターを東京リージョンに作成
$ gcloud container clusters create --num-nodes=2 nodejs-cluster \
--zone asia-northeast1-a \
--machine-type g1-small \
--enable-autoscaling --min-nodes=2 --max-nodes=5

Creating cluster nodejs-cluster...
kubeconfig entry generated for nodejs-cluster.
NAME            ZONE               MASTER_VERSION  MASTER_IP       MACHINE_TYPE  NODE_VERSION  NUM_NODES  STATUS
nodejs-cluster  asia-northeast1-a  1.6.4           35.187.xxx.xxx  g1-small      1.6.4         2          RUNNING

GKEでは、Google Compute EngineのVMインスタンスをNodeとして利用しています。このNode内でPodが動作します。

ここでオートスケールの設定をしておくと、Podが増えすぎてリソース不足になってしまいPodが追加できなくなってもNodeを自動的に作成し、Podを追加できるようにしてくれます。
急にアクセスが増えてWebサーバー用のPodを追加するというような用途で利用できます。ただし、Nodeを作成するということは、VMインスタンスを作成するということでもあるため、Node立ち上げには30秒以上はかかります。

後述しますが、Podにも並列オートスケール機能が備わっており、特定のPodのリソースが不足した場合に同じPodを自動的に増やすこともできます。
Podの追加は1, 2秒で済むため、Nodeのリソースにはある程度は余裕を持たせたほうが高速にスケールアウトできます。

Dockerイメージのアップロード

GCPにはContainer Registryというサービスがあり、ローカルで作成したDockerイメージをアップロードすることができます。
Docker HubにDockerイメージをアップロードすると、不特定多数にイメージが公開されてしまいますが、Container Registryではプライベート環境として利用することができるので、Dockerイメージをセキュアに共有することが可能です。

それでは、ローカルでContainer Registry用にDockerイメージをビルドして、アップロードしてみます。
初めてContainer Registryを利用する場合には、GCPの管理コンソールで利用可能な状態にしておいてください。

# GCPプロジェクトIDを環境変数に入れる(今回の例では kubernetes-demo-172306)
$ GCP_PROJECT=$(gcloud config get-value project)
$ echo $GCP_PROJECT
kubernetes-demo-172306

# Container Registryの格納先をタグにしてDockerイメージをビルドする
# タグの形式は、asia.gcr.io/[GCP_PROJECT_ID]/[IMAGE_NAME]:[TAG]
$ docker build -t asia.gcr.io/$GCP_PROJECT/nodejs:1.0 .

$ docker images
REPOSITORY                                  TAG                 IMAGE ID            CREATED             SIZE
asia.gcr.io/kubernetes-demo-172306/nodejs   1.0                 ad780aab2439        21 seconds ago      70.5 MB

# DockerイメージをContainer Registryにアップロード
$ gcloud docker -- push asia.gcr.io/$GCP_PROJECT/nodejs:1.0
The push refers to a repository [asia.gcr.io/kubernetes-demo-172306/nodejs]
ce77bebfcbc9: Pushed
5a27379b8054: Pushed
2d746cdb49cd: Pushed
2829efa70233: Pushed
d7d38a95e657: Pushed
5accac14015f: Pushed
1.0: digest: sha256:10a57a2b62c1ba3767c6d8ee9a2f84a9ef9e2e2da6b66ab5e9160cccadd32ea4 size: 1579

Container Registryを開き、ローカルでビルドしたDockerイメージが登録されていることを確認してください。

Deployment

次は、アップロードしたこのイメージからコンテナをGKEのPodとしてデプロイします。

$ kubectl run nodejs-deploy \
--image=asia.gcr.io/$GCP_PROJECT/nodejs:1.0 \
--replicas=1 \
--port=3000 \
--limits=cpu=200m \
--command -- node app/server.js

deployment "nodejs-deploy" created

# pod一覧を取得。STATUSがRunnnigになっていればPodは正しく動作している。
$ kubectl get pod
NAME                             READY     STATUS    RESTARTS   AGE
nodejs-deploy-4288962420-nfsr8   1/1       Running   0          13s

Serviceを作成し、ロードバランシングで外部公開

デプロイしたNode.js用のPodを外部からアクセスできるようにするため、Seviceを作成します。

# Serviceを作成し、ロードバランサでPodを外部に公開
$ kubectl expose deployment nodejs-deploy --port=80 --target-port=3000 --type=LoadBalancer

service "nodejs-deploy" exposed

# ロードバランサにIPアドレスが割り当てられるのを確認する
$ watch kubectl get service
NAME         CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubernetes   10.27.240.1   <none>        443/TCP   57m
nodejs-deploy   10.27.242.119   <pending>   80:32413/TCP   13s

# しばらく待つとIPアドレスが紐付けられる
nodejs-deploy   10.27.242.119   35.187.xxx.xxx   80:32413/TCP   52s

http://<EXTERNAL-IPに割り当てられたアドレス>/ にアクセスし、「Hello World!」が表示されたら成功です。GKE上で動作しているNode.jsコンテナへロードバランサを通してアクセスできています。

Pod台数を増やしてスケールしてみる

ここからがKubernetesの真骨頂です。

Node.jsアプリケーション用のPodは1つしか作成していないので、これだけでは大量のアクセスを捌くことができません。そこでこのPodを10個まで増やしてみます。

$ kubectl scale deploy nodejs-deploy --replicas=10

# watchコマンドで監視すると、作成されたPodがRunningになっていくのがわかります
$ watch kubectl get pod
NAME                             READY     STATUS              RESTARTS   AGE
nodejs-deploy-4288962420-1jznl   0/1       Pending             0          3s
nodejs-deploy-4288962420-bq9xm   0/1       Pending             0          3s
nodejs-deploy-4288962420-cfwrw   0/1       Pending             0          3s
nodejs-deploy-4288962420-d410r   0/1       Pending             0          3s
nodejs-deploy-4288962420-k0rqs   1/1       Running             0          3s
nodejs-deploy-4288962420-mn85w   0/1       ContainerCreating   0          3s
nodejs-deploy-4288962420-n0q4p   1/1       Running             0          5m
nodejs-deploy-4288962420-rh1v9   0/1       Pending             0          3s
nodejs-deploy-4288962420-s0g8s   0/1       Pending             0          3s
nodejs-deploy-4288962420-s5x7t   0/1       Pending             0          3s

しばらく待っても複数のpodがpending状態のままの場合、nodeに作成できるPod数に限界が来ています。コンテナクラスタにオートスケールを設定している場合、nodeを新たに作成してPodを作成できるようにしてくれます。

# get nodeでnode一覧を表示すると、2つだったnodeが4つに自動的に増えていきます
$ watch kubectl get node
NAME                                            STATUS    AGE       VERSION
gke-nodejs-cluster-default-pool-d264d025-fglb   Ready     1h        v1.6.4
gke-nodejs-cluster-default-pool-d264d025-hv90   Ready     3m        v1.6.4
gke-nodejs-cluster-default-pool-d264d025-k5q3   Ready     3m        v1.6.4
gke-nodejs-cluster-default-pool-d264d025-m62m   Ready     1h        v1.6.4

# Nodeが4つになったら、全てのPodがRunningになりました!
$ watch kubectl get pod
NAME                             READY     STATUS    RESTARTS   AGE
nodejs-deploy-4288962420-1jznl   1/1       Running   0          7m
nodejs-deploy-4288962420-bq9xm   1/1       Running   0          7m
nodejs-deploy-4288962420-cfwrw   1/1       Running   0          7m
nodejs-deploy-4288962420-d410r   1/1       Running   0          7m
nodejs-deploy-4288962420-k0rqs   1/1       Running   0          7m
nodejs-deploy-4288962420-mn85w   1/1       Running   0          7m
nodejs-deploy-4288962420-n0q4p   1/1       Running   0          12m
nodejs-deploy-4288962420-rh1v9   1/1       Running   0          7m
nodejs-deploy-4288962420-s0g8s   1/1       Running   0          7m
nodejs-deploy-4288962420-s5x7t   1/1       Running   0          7m

Rolling Update

次は、ローカルでNode.jsアプリケーションの更新がされ、稼働中のPodに更新内容を反映してみましょう。

まずは、ローカルでserver.jsのメッセージをHello World!からHello GKE World!に変更します。

server.js
app.get('/', function (req, res) {
    res.render('index', {
        title: 'Kubernetes Demo',
        message: 'Hello GKE World!'
    });
});

Dockerイメージのタグにつけるバージョン番号を1.0から2.0に変更して、Dockerイメージをビルドし、Container RegistryにPUSHします。

$ docker build -t asia.gcr.io/$GCP_PROJECT/nodejs:2.0 .
$ gcloud docker -- push asia.gcr.io/$GCP_PROJECT/nodejs:2.0

set image コマンドでタグ2.0の新しいイメージをPodに設定させます。
同一のPodが2つ以上稼働している場合、Rolling Updateという機能で既に稼働しているPodが1つずつ入れ替わっていきます。全ての新しいPodが作成されるまで、古いPodは削除されないため、ダウンタイムを発生させることなくPodを最新のものに入れ替えることができます。

$ kubectl set image deployment/nodejs-deploy nodejs-deploy=asia.gcr.io/$GCP_PROJECT/nodejs:2.0
deployment "nodejs-deploy" image updated

# Rolling UpdateでPodが新しいものに順次切り替わっていくのを眺めます
$ watch kubectl get node
NAME                             READY     STATUS        RESTARTS   AGE
nodejs-deploy-105340789-lkz9j    1/1       Running       0          19s
nodejs-deploy-105340789-rw45w    0/1       Pending       0          19s
nodejs-deploy-105340789-z3xld    0/1       Pending       0          16s
nodejs-deploy-4288962420-2964m   1/1       Terminating   0          7m
nodejs-deploy-4288962420-7zmw6   1/1       Running       0          7m
nodejs-deploy-4288962420-8sfpq   1/1       Running       0          7m
nodejs-deploy-4288962420-cdg2h   1/1       Running       0          7m
nodejs-deploy-4288962420-k0rqs   1/1       Running       0          41m
nodejs-deploy-4288962420-kkx08   1/1       Running       0          7m
nodejs-deploy-4288962420-n0q4p   1/1       Running       0          46m
nodejs-deploy-4288962420-p1m03   1/1       Running       0          7m
nodejs-deploy-4288962420-qxhm8   1/1       Running       0          7m
nodejs-deploy-4288962420-z8z2z   1/1       Terminating   0          7m

全てのPodが切り替わったら、再びブラウザで同じURLにアクセスしてみます。
「Hello GKE World!」に切り替わっていたらローリングアップデートが成功です。

デプロイ履歴とロールバック

アップデートした新バージョンに不具合が見つかった場合、Deploymentのヒストリーを辿ってロールバックすることもできます。

$ kubectl rollout history deployment/nodejs-deploy
deployments "nodejs-deploy"
REVISION        CHANGE-CAUSE
1               <none>

# ロールバックで1つ前に戻す
$ kubectl rollout undo deployment/nodejs-deploy

GKEデモ環境の削除

今回のデモはここまでで終了です。
このまま放置するとGCPで課金されてしまうため、不要な場合は下記コマンドで削除してください。
次のエントリでもこのクラスタを利用するので、続けて試したい場合はまだ削除しないことをオススメします。

$ kubectl delete deployment,service,pod --all
$ gcloud container clusters delete nodejs-cluster

最後に

今回はコンソールコマンドでKubernetesを操作しただけでしたが、同様の設定はYAMLファイルに記載して実行することができます。

コマンドだけの運用ではオペレーションミスが発生する可能性があるため、実際の運用では設定ファイルもソース管理してミスを防ぐチェックをする体制を構築したほうがよいでしょう。

次回の記事は、別のエントリで設定ファイルによるデプロイ手順についてです。

※追記

Kubernetesを用いてGoogle Container Engineでコンテナクラスタをデプロイする〜設定ファイル編〜