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を作成することができるので、
開発者に配ったりも楽になるはずです。

2014年9月4日木曜日

[GCP] Compute EngineのContainer-Optimized Images


最近GCEばっかりやってます。 GCE上で良いシステム構築パターン見たいな物を見つけたいなーと思っていて、 Kubernetes、Deployment Managerと触ってきましたが、 今回のが一番しっくり来ている気がする Container−Optimized Imagesについて書きます。

Container-Optimized Imagesとは?

Container-Optimized Imagesとは名前の通りGoogle Compute Engine上でコンテナ(現状だとDocker)の扱いに 最適化されたImageで以下のようなものを最初から内包しています。
  • Debian 7
  • Docker rumtime
  • Kubernetes Kubelet

Kubelet?

KubeletはKubernetesのコンテナ起動・維持の役割を行っているコンポーネントで、 manifestファイルベースでDockerコンテナを立てることができます。 またコンテナ自体の監視・維持(ヘルスチェック)も行っておりコンテナが落ちても、 立ち上げ直してくれたりします。

Container-Optimized Imagesを使ってみる。

Container-Optimized ImagesはOpen PreviewなのでGCPを触ったことがあればすぐに利用することができます。 GCP触るのでGoogle Cloud SDKはインストールしておきます。

1. インスタンス内で立てるDockerコンテナのmanifestファイルを作成する

インスタンスを建てる前に、インスタンス内で立てるDockerコンテナのmanifestファイルを作成します。 Container-Optimized Imagesはインスタンス起動時にDockerコンテナを一緒に建てることも可能で、 せっかくなので起動と同時にDockerコンテナを建てたいと思います。
manifestファイルの仕様はここにあります。 通常のdocker runの様に、image、ports(Port Forwarding)、workingDir、、volume、command、envあたりが設定できます。
今回は単純にgoogleで公開しているgolang-helloコンテナを立ててみたいと思います。
containers.yaml
version: v1beta2
containers:
  - name: app
    image: google/golang-hello
    ports:
      - name: app-http
        hostPort: 80
        containerPort: 8080
当然複数のコンテナが必要な場合は複数指定することもできます。

2. インスタンスを起動

Cloud SDK経由でContainer−Optimized Imagesのimageを指定してインスタンスを起動します。 ※このContainer-Optimized ImagesはCloud Consoleでは表示されないため、画面から起動することはできません。
Container-Optimized ImagesはGCPのmetadataで指定されたmanifestを元にDockerコンテナをインスタンス内で起動します。 ※当然後から起動することもできます。(後述) インスタンスの起動時にmanifestをmetadataに詰めるには以下のようにします。
$ gcloud compute instances create container-optimized-instance \
  --image container-vm-v20140826 \
  --image-project google-containers \
  --zone asia-east1-a \
  --machine-type f1-micro \
  --metadata-from-file google-container-manifest=containers.yaml
なお最新のContainer−Optimized Imagesを調べたい場合は以下のコマンドを起動してください。
$ gcloud compute images list --project google-containers
最後の--metadata-from-file google-container-manifest=/path/to/manifest.yamlがコンテナのmanifestを指定している部分です。

3. 確認してみる

では実際に起動したinstanceへ入って、コンテナが起動しているか確認してみます。 ※起動後go-hello containerが起動するまで数十秒かかります。
$ gcloud compute ssh container-optimized-instance
[container-optimized-instance] $ sudo docker ps

CONTAINER ID        IMAGE                        COMMAND                CREATED             STATUS              PORTS                    NAMES
729a0956ef26        google/golang-hello:latest   "/bin/go-run"          6 seconds ago       Up 5 seconds                                 k8s--app.f546c6c--1.http--050b0083                
ca388e45c789        google/cadvisor:latest       "/usr/bin/cadvisor -"   2 minutes ago       Up 2 minutes                                 k8s--cadvisor.1207d44b--cadvisor_-_agent.file--4b947991
13eb7732a79a        kubernetes/pause:latest      "/pause"               2 minutes ago       Up 2 minutes        0.0.0.0:4194->8080/tcp   k8s--net.46426d55--cadvisor_-_agent.file--064d8229
42bbafd3efe0        kubernetes/pause:latest      "/pause"               2 minutes ago       Up 2 minutes        0.0.0.0:80->8080/tcp     k8s--net.5dc56e59--1.http--04c39388               

[container-optimized-instance] $ echo "コンテナにアクセスできるか確認"
[container-optimized-instance] $ curl http://localhost
href
="/42">Found.
起動していますね。 もちろんCompute Engineの80ポートを開けて、このインスタンスのIPアドレスにhttpアクセスすれば外部からアクセスも可能です。 Kubeletが監視をしている為、コンテナを落としてもまた起動してくれます。
[container-optimized-instance] $ sudo docker rm -f 729
729
[container-optimized-instance] $ sudo docker ps

CONTAINER ID        IMAGE                     COMMAND                CREATED             STATUS              PORTS                    NAMES
ca388e45c789        google/cadvisor:latest    "/usr/bin/cadvisor -"   6 minutes ago       Up 6 minutes                                 k8s--cadvisor.1207d44b--cadvisor_-_agent.file--4b947991
13eb7732a79a        kubernetes/pause:latest   "/pause"               7 minutes ago       Up 7 minutes        0.0.0.0:4194->8080/tcp   k8s--net.46426d55--cadvisor_-_agent.file--064d8229   
42bbafd3efe0        kubernetes/pause:latest   "/pause"               7 minutes ago       Up 7 minutes        0.0.0.0:80->8080/tcp     k8s--net.5dc56e59--1.http--04c39388                  

[container-optimized-instance] $ echo "しばらくまつ"
[container-optimized-instance] $ sudo docker ps

CONTAINER ID        IMAGE                        COMMAND                CREATED             STATUS              PORTS                    NAMES
9c0b7e8c50c6        google/golang-hello:latest   "/bin/go-run"          36 seconds ago      Up 36 seconds                                k8s--app.f546c6c--1.http--3f3e8890                
ca388e45c789        google/cadvisor:latest       "/usr/bin/cadvisor -"   7 minutes ago       Up 7 minutes                                 k8s--cadvisor.1207d44b--cadvisor_-_agent.file--4b947991
13eb7732a79a        kubernetes/pause:latest      "/pause"               7 minutes ago       Up 7 minutes        0.0.0.0:4194->8080/tcp   k8s--net.46426d55--cadvisor_-_agent.file--064d8229
42bbafd3efe0        kubernetes/pause:latest      "/pause"               7 minutes ago       Up 7 minutes        0.0.0.0:80->8080/tcp     k8s--net.5dc56e59--1.http--04c39388               
確かにgolang-helloコンテナが復活しています。

4. 後からコンテナを起動する

インスタンスを起動後にコンテナを起動する方法は2つあります。
1つ目はmetadataを更新して、インスタンスを再起動する方法です。
$ gcloud compute instances add-metadata container-optimized-instance --metadata-from-file google-container-manifest=updated-manifest.yaml
$ gcloud compute instances reset container-optimized-instance
公式のサンプルではこの方法でやっています。 ちょっと微妙ですね...
2つ目の方法は直接Kubeletにmanifestを認識させる方法です。 KubeletはKubernetesの公式ページに書いてある限りでは、以下の方法でmanifestを取り込めます。
  • HTTP Endpoints
  • etcd
  • File
  • HTTP Server
HTTP EndpointsとHTTP Serverの差はよくわからないです。 ただ現状ざっと見た感じだと、Container-Optimized Imagesの場合fileでのやり方しか出来なそうな気がしています。 ※調査不足です。
なので一旦ファイルでのやり方をやってみます。 Kubeletは20秒に1回 起動時に指定されたディレクトリを監視していてそこにmanifestが入るとそれに従ってコンテナを起動したり落としてくれます。 Container−Optimized Imagesの場合だと(デフォルト?)_etc/kubernetes/manifests/になります。
$ gcloud compute ssh container-optimized-instance
[container-optimized-instance] $ vi updated-manifest.yaml
[container-optimized-instance] $ sudo cp updated-manifest.yaml /etc/kubernetes/manifests/
これであとから起動したり、containerを変えたりできます。 もちろんkubeletをかえさず、dockerコマンドでも良いですが、多分Kubeletが監視をしてくれないです。

まとめ

Container-Optimized ImagesはMaster無しの Kubernetesという感じで、結構使いやすい感じですね。 Kubernetesでどういう恩恵がウケれるのかもイメージしやすい気がします。
Google Cloud StorageをDocker Registryとして使えるgoogle/docker-registry コンテナイメージを使って、 Private Docker Registryも一緒に立てると更に使いやすいと思います。
開発者の端末と、Container-Optimized ベースのインスタンス内に同一Cloud Storage見るようにDocker Registry立てておいて、 開発時に育てたContainerをPrivate RegistryにPushして、インスタンス側でそれを起動みたいなことをすると 相当素敵そうですね。
その辺りの建て方もその内かけたら思います。

2014年9月3日水曜日

[GCP] Google Cloud PlatformのCloud Deployment Managerの事始め



最近GCPでGitlabやRailsの開発環境スタックがOne-ClickでGCP上にデプロイできるようになりました。

GCPではコレ以外にも、Apache Cassandra、MEAN Stack、Mongo DB、RabbitMQなどの環境をOne-Clickでデプロイできるようになっています。
これらの環境をデプロイする技術は、単純なVMイメージコピーではなくプロビジョニングしている様に見えます。
このOne-Click Deployを提供しているのがCloud Deployment Managerと呼ばれる、 GCP上のサービスです。

Cloud Deployment Manager is 何?

Cloud Deployment Manager(めんどくさいので以降CDM)はGCPに対して、
デプロイや、セットアップをテンプレートベースで定義することができるAPIとサービスです。
例えば上記に有るようなMEAN StackやRuby on Rails環境をテンプレートとして記述し、
いつでもGCP上にデプロイすることができるようになります。
なんとなくAWSでいうところのCloud Formationに似ている気がしますね
なお現状(2014/09/02)はLimited Previewなサービスのため、利用するにはサインアップが必要です。
この辺を参考にやってみてください。

CDMの2つのビルドプロセス(リソース)

CDMでは大きく分けて2つのビルドプロセスが有ります。
1つはGCEのセットアップ情報を記述したテンプレートの作成と登録 (template リソース)、
もう一つはテンプレートを元にした実際のデプロイ(deployment リソース)
templateはリージョンに関係ないグローバルリソースで、
deploymentはリージョン固定のリソースになります。
それぞれはCLIやAPIを利用して、操作することができます。

CDMでやれること

CDMでは現状以下のGCEリソースを定義することができます。
※細かい設定は置いておいて
  • Replica Pool
    • ほぼほぼ GCEインスタンスと同じ
    • 別途Actionというものを定義することによりinitスクリプトを定義できる
  • Autoscaling Group
    • オートスケールの仕方設定
    • これもLimited Preview
  • Firewall
    • 普通にfirewall
  • Health Check
    • LB用のヘルスチェック定義
  • Network
    • GCEのネットワークを設定できる
    • ただしAPIリファレンス上は乗っているが、ドキュメント上に無いので試してみないとなんとも...
  • Load Balancing
    • LBの設定
またActionという仕組みにより、
インスタンス起動時に流すコマンドをまとめたものを定義することもできる ※後述

自分で書いてみる Jenkinsのテンプレートを作ってみる

テンプレートを作る

まずは結構簡単に入れられるJenkinsのテンプレートを作ってみたいと思います。
構成は1台のGCEインスタンスにnginx-tomcat-jenkinsが乗っている感じです。
このテンプレートは以下になります。
name: nginx_tomcat_jenkins
modules:
  nginx_tomcat_jenkins:
    type: REPLICA_POOL
    replicaPoolModule:
      numReplicas: 1
      replicaPoolParams:
        v1beta1:
          machineType: n1-standard-1
          zone: asia-east1-a
          baseInstanceName: nginx_tomcat
          disksToCreate:
            - boot: true
              initializeParams:
                sourceImage: https://www.googleapis.com/compute/v1/projects/centos-cloud/global/images/centos-6-v20140718
                diskSizeGb: 10
          initAction: install_jenkins
          networkInterfaces:
            - network: default
              accessConfigs:
                - name: External NAT
                  type: ONE_TO_ONE_NAT
      envVariables:
        JENKINS_HOME:
          value: /data/jenkins
actions:
  install_jenkins:
    commands: [
      "yum -y -q update",
      "yum -y -q install wget tar",
      "export JENKINS_HOME=$JENKINS_HOME",
      "%file:java_install.sh",
      "%file:nginx_setup.sh",
      "%file:tomcat_setup.sh",
      "%file:jenkins_setup.sh",
      "%file:run.sh"
    ]
解説をすると
テンプレートではmodulesactionsの2つを設定していきます。
modulesではインスタンスや、LB、ネットワーク、オートスケールなどのリソースの設定を行います。
どのリソースかは、type要素で判別します。
actionsではインスタンス起動時のスクリプトを設定します。
%file:の形式で書いてあるものはテンプレートと同じディレクトリ内にあるファイルを埋め込んでくれる書式で、
YAMLでテンプレートを書いた場合のみ利用できます。
modulesの下のnginx_tomcat_jenkinsはmodule名で任意に名前を付けられます。
今回はGCEのインスタンスを作成するのでこのmoduleのtypeはREPLICA_POOLにしてあります。
replicaParamModule以下でインスタンスの設定を行っていきます。
initActionの部分でactions配下のactionを指定して、どういったイニシャルスクリプトを流すかを設定しています。
これらの値は基本的にテンプレート作成後、デプロイを行う時に上書きすることができます。
なお起動シェルも含めたコードは以下に有ります。
https://github.com/soundTricker/cdm-jenkins-template

テンプレートを登録する。

テンプレートの登録はCloud Deployment Manager API を利用するか、
Google Cloud SDK CLIを利用することで実行できます。
今回はCLIを使って登録を行っていきます。
まずDeployment ManagerはまだLimited Previewの為、通常のままではCLIにコマンド群が入っていません。
Cloud SDKのpreview featureをインストールします。
$ gcloud components update preview
取得後デフォルトのプロジェクトを設定しておきます。
$ gcloud config set project PROJECT-ID
設定が終わったらテンプレートを登録します。
今回は上記で作成したテンプレート(jenkins.yaml)をjenkinsというテンプレート名で登録します。
$ gcloud preview deployment-manager templates create --template-file jenkins.yaml jenkins
登録できたかは以下で確認できます。
$ gcloud preview deployment-manager templates list

$ gcloud preview deployment-manager templates get jenkins
なおlistの方は名前だけが取得されgetの場合%file:が展開された詳細なテンプレートが取得できます。

テンプレートを利用してデプロイする。

次にデプロイを行います。 デプロイもCLI経由で行います。
$ gcloud preview deployment-manager deployments --region asia-east1 create --template jenkins jenkins-dm
なお先ほど記述した上書きを行う場合は以下のように--overridesで指定します。
$ gcloud preview deployment-manager deployments --region asia-east1 create --template jenkins --overrides $.modules.nginx_tomcat_jenkins.replicaPoolModule.numReplicas=2 jenkins-dm
しばらく待つとインスタンスが上がり、actionsで指定したスクリプトが実行されます。

まとめ

やってみると結構お手軽ですね。
ただ今回のスクリプトの場合はGCEインスタンスが1台ですし、サーバセットアップの部分は正直だたのshellなので、
ansibleやchef等のプロビジョニングアプリケーションを利用したほうが楽に思えます。
というよりプロビジョニングするぐらいならimages作ってCloud Design Patternで言うところの
Stamp Patternを利用して複製したほうが楽な気がします。
Deployment Managerの使いドコロは個人的にはここではないように思えます。
※現状GoogleがリリースしているMEAN StackやRoRのOne-Click Deployには意味があると思いますが。
次はもう少し複数台を配置するような設定を書いてみたいと思います。