この記事は Kubernetes Advent Calendar の 20 日目の記事です。
概要
CircleCI には実はテストが通った後に Kubernetes にデプロイする機能が存在します。
ここでは、GCE に Kubernetes 環境を構築し、そこに Private Dokcer Repository を作成し、
CircleCI でテストが通ったアプリケーションを Kubernetes 上にデプロイする方法について説明します。
Kubernetes 環境を手に入れる
ここはさくさく行きます、前述のとおり GCE を使います。
この辺りは今までの AdventCalendar や Qiita の記事を読みましょう。
事前に gcloud の components を update しておきましょう。(忘れててちゃんと動かなかった)
$ gcloud components update
Kubernetes を git から clone してきて、以下のコマンドを実行します。
$ make release
もしかしたら boot2docker 等の docker 環境を要求されるかもしれません。用意しておきましょう。
次の手順では、minion から GCS を使うために、cluster/gce/config-default.sh
を編集し、
minion の scopes を storage-full
にしておきます。
--- a/cluster/gce/config-default.sh
+++ b/cluster/gce/config-default.sh
@@ -17,10 +17,10 @@
# TODO(jbeda): Provide a way to override project
# gcloud multiplexing for shared GCE/GKE tests.
GCLOUD=gcloud
ZONE=us-central1-b
MASTER_SIZE=n1-standard-1
MINION_SIZE=n1-standard-1
NUM_MINIONS=4
# TODO(dchen1107): Filed an internal issue to create an alias
# for containervm image, so that gcloud will expand this
# to the latest supported image.
@@ -34,7 +34,7 @@ MINION_TAG="${INSTANCE_PREFIX}-minion"
MINION_NAMES=($(eval echo ${INSTANCE_PREFIX}-minion-{1..${NUM_MINIONS}}))
CLUSTER_IP_RANGE="10.244.0.0/16"
MINION_IP_RANGES=($(eval echo "10.244.{1..${NUM_MINIONS}}.0/24"))
-MINION_SCOPES=("storage-ro" "compute-rw")
+MINION_SCOPES=("storage-full" "compute-rw")
# Increase the sleep interval value if concerned about API rate limits. 3, in seconds, is the default.
POLL_SLEEP_INTERVAL=3
PORTAL_NET="10.0.0.0/16"
(END)
上記の設定が完了したら、Kubernetes 環境を GCE 上に起動します。
cluster/kube-up.sh
kube-up.sh
は 昔書いた時より色々進化してて感動しました。
やっていることや作られる VM/ストレージは同じですので、
内部で何が起きているか気になる方は昔書いた記事をご覧ください。
認証情報は標準出力でなく ~/.kunernetes-auth
に出力されるようになった模様です。
Kubernetes 上の Private Docker Repository を手に入れる
google/docker-registry を使う
CircleCI との連携のために docker をがっつり使うので、
private docker repository を用意しましょう。
google/docker-registry を使います。
google/docker-registry
は GCS 上に private docker repository を持つための物です。
せっかくなので、これも Kubernetes 上に乗せてしまうことにします。
事前に GCS に bucket を作っておきます。bucket の名前は後で設定ファイル内で使う事になるので記録しておきましょう。
$ gsutil mb -l ASIA gs://rrreeeyyy_docker_registry_bucket
Kubernetes に乗せるために、google/docker-registry
のコントローラを作成します。
設定ファイルは以下のようになります。
{
"id": "dockerRegistryController",
"kind": "ReplicationController",
"apiVersion": "v1beta1",
"desiredState": {
"replicas": 2,
"replicaSelector": {"name": "dockerregistry"},
"podTemplate": {
"desiredState": {
"manifest": {
"version": "v1beta1",
"id": "dockerregistry",
"containers": [{
"name": "dockerregistry",
"image": "google/docker-registry",
"cpu": 200,
"ports": [{"containerPort": 5000, "hostPort": 5000}],
"env": [{"name": "GCS_BUCKET", "value": "rrreeeyyy_docker_registry_bucket"}]
}]
}
},
"labels": {
"name": "dockerregistry",
}
}
},
"labels": {"name": "dockerregistry"}
}
ポイントは、以下のように GCS_BUCKET
に先ほど作った bucket の名前を登録しておくことです。
"env": [{"name": "GCS_BUCKET", "value": "rrreeeyyy_docker_registry_bucket"}]
以下のコマンドでコントローラを作成します。
$ cluster/kubectl.sh create -f docker-registry-controller.json
次に、どの minion に配置されても参照できるように、サービスを作成します。
設定ファイルは以下のようになります。
{
"id": "dockerregistry",
"kind": "Service",
"apiVersion": "v1beta1",
"port": 5000,
"containerPort": 5000,
"labels": {
"name": "dockerregistry"
},
"selector": {
"name": "dockerregistry"
}
}
サービスを作成します。
$ cluster/kubectl.sh create -f docker-registry-service.json
minion に対して 5000 番を開放する GCE のファイアウォールルールを追加します。
$ gcloud compute firewall-rules create --allow=tcp:5000 --target-tags=kubernetes-minion kubernetes-minion-5000
ここまで来たら、Pod が実際に作成されているか確認してみましょう。
$ cluster/kubectl.sh get pods
NAME IMAGE(S) HOST LABELS STATUS
04f4c9f8-8771-11e4-b820-42010af08e38 google/docker-registry kubernetes-minion-1.c.xxxx-xxxx-123.internal/xxx.xxx.xxx.xxx name=dockerregistry Running
04f629c1-8771-11e4-b820-42010af08e38 google/docker-registry kubernetes-minion-2.c.xxxx-xxxx-123.internal/xxx.xxx.xxx.xxx name=dockerregistry Running
どちらかの minion の :5000
に対して curl を打ってみましょう。
$ curl xxx.xxx.xxx.xxx:5000
"docker-registry server (prod) (v0.8.1)"
このように表示されれば docker-registy がちゃんと Kubernetes 上で動いていそうです。
(※実際に使用する時はアクセス制限や basic 認証をちゃんと投入する必要がありそうです。)
CircleCI と連携する
これで Private Docker Repository を手に入れることが出来ました。
さて、CircleCI と連携してみましょう。
CircleCI が公開している、テスト用の Rails アプリを使ってみましょう。
(circleci/docker-hello-google)
circle.yml
を見ると以下のようになっています。
machine:
services:
- docker
dependencies:
cache_directories:
- ~/kubernetes
pre:
- ./ensure-kubernetes-installed.sh
- docker build -t $EXTERNAL_REGISTRY_ENDPOINT/hello:$CIRCLE_SHA1 .
test:
post:
- docker run -d -p 3000:3000 -e "SECRET_KEY_BASE=abcd1234" $EXTERNAL_REGISTRY_ENDPOINT/hello:$CIRCLE_SHA1; sleep 10
- curl --retry 10 --retry-delay 5 -v http://localhost:3000
deployment:
prod:
branch: master
commands:
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS $EXTERNAL_REGISTRY_ENDPOINT
- envsubst < .kubernetes_auth.template > ~/.kubernetes_auth
- ./deploy.sh
ポイントは、dependencies で Kubernetes をインストールしている所と、
deployment の所で ./deploy.sh
を実行している所です。
ensure-kubernetes-installed.sh
は以下のようになっています。
#!/bin/bash
# Exit on any error
set -e
KUBERNETES_VERSION=41eb15bcff4f114e95788f1e3a5ad3645c4e53fd
# Exit if already installed
if [[ -d ~/kubernetes ]]; then
echo "Kubernetes already installed"
exit 0
else
echo "Installing Kubernetes..."
fi
# Clone repo
(cd ~ && git clone https://github.com/GoogleCloudPlatform/kubernetes.git)
(cd ~/kubernetes && git reset --hard $KUBERNETES_VERSION)
# Build go source
(cd ~/kubernetes && hack/build-go.sh)
どうやら普通に Kubernetes をインストールしているだけみたいです。
二回目以降はキャッシュするので、Kubernetes already installed
と表示されてスキップされていました。
deployment の部分ですが、deploy.sh
は以下のようになっています。
#!/bin/bash
# Exit on any error
set -e
KUBE_CMD=${KUBERNETES_ROOT:-~/kubernetes}/cluster/kubecfg.sh
# Deploy image to private GCS-backed registry
docker push $EXTERNAL_REGISTRY_ENDPOINT/hello:$CIRCLE_SHA1
# Update Kubernetes replicationController
envsubst < kubernetes/rails-controller.json.template > rails-controller.json
$KUBE_CMD -c rails-controller.json \
update replicationControllers/railscontroller
# Roll over Kubernetes pods
$KUBE_CMD rollingupdate railscontroller
こちらは、$EXTERNAL_REGISTRY_ENDPOINT
に対して Docker image を pushし、コントローラを登録した上で、rollingupdate を掛けています。
rollingupdate は update-demo に解説がありますが、pod を 1 台ずつアップデートする命令のようです。
というわけで、早速このリポジトリを fork して、CircleCI に掛けてみました。
CircleCI に掛ける際は、以下の環境変数をセットしないと、deployment が途中で止まってしまうので注意して下さい。
- KUBE_MASTER_IP
- KUBERNETES_PASS
- KUBERNETES_USER
- DOCKER_USER
- DOCKER_EMAIL
- DOCKER_PASS
- RAILS_SECRET
- INTERNAL_REGISTRY_ENDPOINT
- EXTERNAL_REGISTRY_ENDPOINT
Kubernetes は、上記の環境変数がセットされていない場合には、プラットフォームに応じたコマンドで自動的に上記の値を検出しようとします。
例えば、KUBE_MASTER_IP
がセットされていないような場合には、gcutil
等のコマンドを実行するようです。
CI 環境には gcutil
のようなコマンドは入っていないため、そこで deployment が止まってしまいます。
また、コンテナ内で最新の kubernetes を使いたい場合は以下を設定する必要がありそうです。
(ensure-kubernetes-installed.sh
のハッシュ値を書き換えることで使う Kubernetes のバージョンを変えられます)
- PROJECT
さて、正しく環境変数をセットしてリポジトリを push すると、CI の最後に以下のように deploy.sh を実行しています。
deploy.sh が実行されたら、手元の kubectl.sh で pods の状況を確認してみましょう。
➜ git:(master) ✗ ./cluster/kubectl.sh get pods
a59fdaf0-8792-11e4-b820-42010af08e38 xxx.xxx.xxx.xxx:5000/hello:latest kubernetes-minion-1.c.xxxxx-xxxxx-xxx.internal/xxx.xxx.xxx.xxx name=rails Running
a5a05c0a-8792-11e4-b820-42010af08e38 xxx.xxx.xxx.xxx:5000/hello:latest kubernetes-minion-2.c.xxxxx-xxxxx-xxx.internal/xxx.xxx.xxx.xxx name=rails Running
しばらくしてから minion の :3000 にアクセスすると、以下のように表示されます。
(GCE 側で :3000 を minion に対して許可する firewall-rule を追加しておきましょう)
感想
一昔前(9月頃)に触った時に比べ、かなりの機能が充実してきました。
今回のように、CI 環境と連携して、テストが通ったら Kubernetes 環境へデプロイ、のようなことも出来るようになっていました。
残る課題は Kubernetes 上にデプロイしたアプリのストレージ永続化です。
現在、GCE を使用した場合には GCS をコンテナがマウントすることが出来るようになっています。
(参考: kubernetes/volumes )
もしくは、minion の特定のディレクトリをコンテナがマウントすることも出来ます。
そのため、分散ストレージ等を minion に用意すればストレージを永続化出来るでしょう。
という環境をしっかりと用意すれば、
例えば社内の Docker 用 PaaS 環境として Kubernetes はうってつけではないでしょうか。
まだ実サービスに投入するにはやや怖いですが、本記事のようにテストしたコンテナを適当に分散して、
再起動等の管理もよしなにやってくれるので、便利なように思えます。
引き続き目が離せないプロダクトの 1 つであると感じました。
まとめ
- GCE+Kubenetes で Docker Private Repository を作成
- Kubernetes + CircleCI でテストが通ったコンテナをデプロイ
- 社内 PaaS とかで使えそう。