2014年9月9日火曜日

[Kubernetes] Kubernetesとgoogle/docker-registryを使ったPrivate Docker Registry開発パターン

Dockerを本番環境で運用しようと考えるとどこにPrivateなDocker Registryを建てるかというのが結構大事になってきます。
本番サーバ上などでDockerfileをビルドするのもできなくは無いですが、
できれば別サーバでビルドして、Docker Registryに突っ込んでおくほうがパターンとしては良さそうです。
今回はGoogle Cloud Storageをdocker-registryとして扱えるgoogle/docker-registryと Kubernetesを使って開発を行っていくパターンについて考えたいと思います。

最終イメージ

最終的なイメージは以下になりました。
後半にこの環境の構築方法を書いていきます。

解説

環境周り

GCP上にはKubernetesをいれており、kubernetes-master 1台(図上には無し)と 複数台のkubernetes-minion(図上は2台)が存在しています。
各minion間のポートは開いており通信が可能です。
また各minionにはGCEのサービスアカウントでstorage-full権限を付けており、
minionからGoogle Cloud Storageに対してのアクセスはfull権限持っている状態になっています。
このKubernetesに対してgoogle/docker-registryのPod(dockerコンテナ)を ReplicationControllerを利用して配置します。
google/docker-registryはGCSをdocker-registryとして利用できるGoogle製のDocker Registoryで minion上に配置されたgoogle/docker-registryはminionに付与されたサービスアカウントを利用して、
GCSに対してアクセスを行います。
例えば5000番ポートでgoogle/docker-registryを立ち上げた場合
[kubernetes-minion1] $ docker pull localhost:5000/my-app
とすればlocalhost:5000に立ち上がっているgoogle/docker-registryが minionのサービスアカウント経由で認証し、
GCS上に保存されているmy-appイメージを取得することができます。



podの配置とminion内の通信

ただ通常KubernetesではReplicationControllerのreplicas(podの配置数)をminion数以下にした場合、
どのminionにpodが配置されるかはわかりません。
全てのminionにgoogle/docker-registryを配置してもよいですが、
その為にサーバリソースを使うのもなんとなくもったいない気がします。
そこでKubernetesのLoad BalancerであるServiceを使って各minion間を通信させます。
ServiceはKubernetesがminion内に作るProxyで、Podに対してPort Forwarding、Load Balancing機能を提供します。
例えばポート5555へのアクセスは、google/docker-registryに向くようにServiceを設定すると、
どのminion内でもポート5555へのアクセスは、いずれかのminionにあるgoogle/docker-registryに Forwardされるようになります。
つまり以下のようにすればminion1でもminion2でもgoogle/docker-registryにアクセスしてくれるようになります。
[kubernetes-minion2] $ docker pull localhost:5555/my-app
実際にはKubernetesで管理するはずのためlocalからkubernetesのマニフェストを作って実行するか、
以下のようにReplicationControllerを作ることになると思います。
[local] $ ./cluster/kubecfg.sh run localhost:5555/my-app 10 myAppController

# kubecfg [OPTIONS] run  image replicas controller  
これでどのminionからでもgoogle/docker-registryを見れるようになりました。
実際にはminionの数や、要件によって、google/docker-registryの数を調整することになると思いますが、
Kubernetesによって管理されているため、google/docker-registryが落ちても立ち上げなおしてくれますし、
replica数の調整もコマンド経由で上げ下げできます
[local] $ ./cluster/kubecfg.sh resize docker_registryController 10

開発時との関連

画像の右側の方のイメージです。
GCSをdocker-registryとして使えるようになることで、
開発者も同じように自分のPC内にgoogle/docker-registryを立てて、imageを取得できるようになります。
またGCS側でACLを設定できるため、更新できる開発者、できない開発者などの制御も(多分)可能です。
※開発者はDockerfileでビルドしろ−でもいい気もしますが。
またjenkins等のCIからも同様にgoogle/docker-registryを立てればどうにでもできるようになります。
素敵ですね。

環境の作成

Cloud Storage側

まずCloud Storage内にDocker Registry用のBucketを作成します。
DOCKER_REGISTRY_BUCKET_NAMEは任意の名前です。 世界レベルでユニークな値なので注意して下さい。
[local] $ $ gsutil mb -l ASIA gs://DOCKER_REGISTRY_BUCKET_NAME

Compute Engine側

Kubernetesを建てる

kubernetesを立てますが、
上記で書いたようにminionからGCSにアクセスできるようにstorage-fullを付ける必要があります。
kubernetesを立ち上げる前であれば、
kubernetesのリポジトリ内にあるcluster/gce/config-default.shMINION_SCOPESを以下のように修正して下さい。
MINION_SCOPES="storage-full" 
もちろんminionからのアクセスはRead-onlyにしたい場合はそれぞれ権限を調整して下さい。
あとはここに書いたようにKubernetesをGCE上に立てて下さい。
※立ち上げてしまっている場合はScopeは修正できないのでminionを作り直す必要があります。

google/docker-registryを建てる

次はこのKubernetes内にgoogle/docker-registryを建てます。
まずはReplicationControllerのマニフェストファイルです。
envのGCS_BUCKETのvalueに先ほど作成したbucket名を設定して下さい
docker-registry-controller.yaml
id: dockerRegistryController
kind: ReplicationController
apiVersion: v1beta1
desiredState: 
  replicas: 1
  replicaSelector:
    name: docker_registry
  podTemplate: 
    desiredState: 
      manifest: 
        version: v1beta1
        id: docker_registry
        containers: 
          - name: docker_registry
            image: google/docker-registry
            ports: 
              - containerPort: 5000
                hostPort: 5000
            env:
              - name: GCS_BUCKET
                #先ほど作成したBucket名
                value: DOCKER_REGISTRY_BUCKET_NAME
    labels: 
      name: docker_registry
labels: 
  name: docker_registry
ReplicationControllerを作成します。
[local] $ ./cluster/kubecfg.sh -c docker-registry-controller.yaml create replicationControllers

google/docker-registryのServiceを設定する

次にどのminionからでもアクセスできるようにServiceを作成します。
docker-registry-service.yaml
id: docker-registry
kind: Service
apiVersion: v1beta1
port: 5555
labels:
  name: docker_registry
selector: 
  name: docker_registry
[local] $ ./cluster/kubecfg.sh -c docker-registry-service.yaml create services
これでCompute Engine側の準備は完了です。

ローカルPC側

google/docker-registryの準備

実際にローカルからgoogle/docker-registry経由でGCSにimageをpushし、
GCE側から取得するところまでやりたいのでローカルPCにも google/docker-registryをいれます。
GCE川の場合はサービスアカウント経由で認証しましたが、
ローカル側の場合は自分のアカウントで認証させます。
詳しい話はここを見て下さい。
今回はgoogle/docker-registry内に認証情報を作成していく方法でやってみたいと思います。
まずgoogle/docker-registry内でgcloud auth loginを行います。
[local] $ docker run -ti --name gcloud-config google/cloud-sdk gcloud auth login
次に上記ログイン済みコンテナ内でGCPプロジェクトを設定させます。
project は自身のプロジェクトにして下さい。
[local] $ docker run -ti --volumes-from gcloud-config google/cloud-sdk gcloud config set project 
最後にこれらの設定済みimageを使ってgoogle/docker-registryを立ち上げます。
GCS_BUCKETは先ほど作成したBucket名を設定して下さい。
[local] $ docker run -d -e GCS_BUCKET=DOCKER_REGISTRY_BUCKET_NAME -p 5000:5000 --volumes-from gcloud-config google/docker-registry
これでローカル側も準備完了です。

試してみる。

実際にローカルからGCSのdocker-registryにimageをpushして、
GCEのKubernetesから利用させてみます。

imageを作る

気分的にgoのwebアプリケーションにしてみます。
まずはDockerfileです。
google/golang-runtimeはDockerfileがあるディレクトリ内にある goファイルをビルドしてアプリケーションとして8080ポートで自動的に立ち会えげてくれる コンテナです。
Dockerfile
From google/golang-runtime
次に使うGoファイル 細かいところは気にしないでください。
main.go
package main

import (
  "fmt"
  "net/http"
  "os"
)

func main() {

  http.HandleFunc("/", index)

  http.ListenAndServe(":8080", nil)
}

func index(rw http.ResponseWriter, req *http.Request) {

  hostname, err := os.Hostname()

  if err != nil {
    fmt.Fprintf(rw, "%s %s", os.Getenv("SERVICE_HOST"), os.Getenv("DOCKER_REGISTRY_SERVICE_PORT"))
  } else {
    fmt.Fprintf(rw, "%s %s %s", hostname, os.Getenv("SERVICE_HOST"), os.Getenv("DOCKER_REGISTRY_SERVICE_PORT"))
  }
}
これらをbuildします。
[local] $ docker build -t localhost:5000/my-app .

GCSへpushする

次にGCSへpushします。
[local] $ docker push localhost:5000/my-app
pushを行うとGCS側にimageがpushされます。

GCE側から使う

KubernetesのReplicationController経由で取得します。
マニフェストを作成します。
先ほどと異なりimageの指定が localhost:5000/my-appではなく、
localhost:5555/my-appになっています。
my-app-controller.yaml
id: myAppController
kind: ReplicationController
apiVersion: v1beta1
desiredState: 
  replicas: 4
  replicaSelector:
    name: myapp
  podTemplate: 
    desiredState: 
      manifest: 
        version: v1beta1
        id: myapp
        containers: 
          - name: myapp
            image: localhost:5555/my-app
            ports: 
              - containerPort: 8080
                hostPort: 80
    labels: 
      name: myapp
labels: 
  name: myapp
これをKubernetesへ登録します。
[local] $ ./cluster/kubecfg.sh -c my-app-controller.yaml create replicationControllers
しばらく待つとminion内でmy-appイメージが立ち上がり始めます。
実際に中に入ってみてみましょう
[local] $ gcloud compute ssh kubernetes-minion-1
[kubernetes-minion-1] $ sudo docker ps

CONTAINER ID        IMAGE                             COMMAND                CREATED             STATUS              PORTS                    NAMES
6221837e9d19        localhost:5555/my-app:latest   "/bin/go-run"          3 hours ago         Up 3 hours                                   k8s--my_-_app.24d4f934--4631a018_-_37ba_-_11e4_-_9aa0_-_42010af07411.etcd--3f808d5b
afaec6e95e26        kubernetes/pause:latest           "/pause"               3 hours ago         Up 3 hours          0.0.0.0:80->8080/tcp     k8s--net.23bc7648--4631a018_-_37ba_-_11e4_-_9aa0_-_42010af07411.etcd--593bb76d
a02f3b43342f        kubernetes/pause:latest           "/pause"               21 hours ago        Up 21 hours         0.0.0.0:4194->8080/tcp   k8s--net.14587872--cadvisor_-_agent.file--d29bb165

[kubernetes-minion-1] $ exit
[local] $ gcloud compute ssh kubernetes-minion-2
[kubernetes-minion-2] $ sudo docker ps

CONTAINER ID        IMAGE                             COMMAND                CREATED             STATUS              PORTS                    NAMES
51da3e224841        localhost:5555/my-app:latest   "/bin/go-run"          3 hours ago         Up 3 hours                                   k8s--my_-_app.4866f942--69dc2f50_-_37ba_-_11e4_-_9aa0_-_42010af07411.etcd--54dd2fcb
e67a30486a10        kubernetes/pause:latest           "/pause"               3 hours ago         Up 3 hours          0.0.0.0:80->8080/tcp     k8s--net.23bc7648--69dc2f50_-_37ba_-_11e4_-_9aa0_-_42010af07411.etcd--5a8179a6
04c624dd7079        google/docker-registry:latest     "/bin/sh -c 'cd /doc"   21 hours ago        Up 21 hours                                  k8s--dockerregistry.8d75af88--491142e3_-_3722_-_11e4_-_9aa0_-_42010af07411.etcd--04646fdc
7042f1deb6d8        kubernetes/pause:latest           "/pause"               21 hours ago        Up 21 hours         0.0.0.0:5000->5000/tcp   k8s--net.c9fe769a--491142e3_-_3722_-_11e4_-_9aa0_-_42010af07411.etcd--90f92c41
9bea90554dd3        google/cadvisor:latest            "/usr/bin/cadvisor -"   21 hours ago        Up 21 hours                                  k8s--cadvisor.20accae6--cadvisor_-_agent.file--64431513
4fe299791621        kubernetes/pause:latest           "/pause"               21 hours ago        Up 21 hours         0.0.0.0:4194->8080/tcp   k8s--net.14587872--cadvisor_-_agent.file--923eced4
実際にdocker-registryが立ち上がっていないminionでもmy-appを取得できていますね。
これでprivate registryを使ったローカル or jenkins → GCS → GCEという形が作れました。

まとめ

GCSを使ってprivateのimageを共有できるのはかなりいいですね。
実際の運用では例えば開発者とminionからはGCSへのpushはread-onlyな状態にして、
Dockerfileのみをコミット可能、
imageのpushはjenkinsのみから実施できるようにしたりすると、危険性も減りそうです。
コンテナを利用していることでポータブルにdocker-registryを作成することができるので、
開発者に配ったりも楽になるはずです。

0 件のコメント:

コメントを投稿