この記事は Kubernetes Advent Calendar 2016 の22日目の記事です。
Kubernetes を使っていると、 Pod 内のコンテナで動くプロセスから使われるデータを GitHub repository の HEAD に同期して更新したいというような場合が出てきます。
例えば、下記のような場合があります。
- CDN を使わずに小規模に配信するコンテンツ
- DBMS に入れるほどではないデータファイル
このような場合に、プロセスだけでなくデータに対してもデプロイフローを作ると複雑になるので、できれば避けたいところです。
今回は、プロセスが使うデータを自動的に GitHub と同期する方法を紹介します。(実際は GitHub に限らず Git 一般に対応可能です。)
gitRepo Volume では駄目なのか
Kubernetes には組み込みの gitRepo Volume という機能があります。
下記のように指定した Git リポジトリの特定リビジョンの内容を自動的に取得して Volume としてマウントすることが可能です。(例より引用)
apiVersion: v1 kind: Pod metadata: name: server spec: containers: - image: nginx name: nginx volumeMounts: - mountPath: /mypath name: git-volume volumes: - name: git-volume gitRepo: repository: "git@somewhere:me/my-git-repository.git" revision: "22f1d8406d464b0c0874075539c1f2e96c253775"
しかし、 `repository` と `revision` 以外の指定ができないので、特定ブランチの HEAD と自動同期するような用途に使えないだけでなく、プライベートリポジトリにも対応できません。
> In the future, such volumes may be moved to an even more decoupled model, rather than extending the Kubernetes API for every such use case.
という記述もあり、 gitRepo ボリュームの今後の発展はあまり見込めません。
## git-sync
先程の gitRepo には下記の記述がありました。
> more decoupled model
これを実現するものの一つが今回の記事で紹介する [git-sync](https://github.com/kubernetes/git-sync) だと考えられます。
git-sync は 特定のパスが Git の指定したブランチもしくはタグもしくはリビジョンの内容を持つように維持し続けるプログラムです。Kubernetes コミュニティにより開発されていますが、Kubernetes API には依存せず、サイドカーコンテナとして使われることを想定しています。
## git-sync の動作確認
ここからは例として nginx コンテナにマウントされたボリュームに属する `/usr/share/nginx/html` 以下を GitHub リポジトリの master ブランチと同期することで、Web コンテンツを配信する Pod を作って動作確認します。
例として実際にはプライベートではない `apstndb/static-content` というリポジトリを使用していますが、各自必要に応じて作成してください。
まず、今回使用する Namespace を用意します。
$ kubectl create namespace git-sync
次に、特定のプライベートリポジトリにアクセスできるようにするために [Deploy Key](https://developer.github.com/guides/managing-deploy-keys/#deploy-keys) に公開鍵を登録します。
なお、今回は次のように生成したキーペアを使用しました。
$ ssh-keygen -t rsa -b 4096 -C "git-sync" -N '' -f ~/.ssh/git-sync
作成した Namespace 内に秘密鍵を Secret としてデプロイします。
$ kubectl create secret generic git-sync-creds --from-file=ssh=${HOME}/.ssh/git-sync --namespace git-sync
下記のような nginx と git-sync を含む Deployment とそれを指す Service を定義したファイルを作成します。
$ cat git-sync-demo.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: git-sync-demo
namespace: git-sync
spec:
replicas: 2
template:
metadata:
labels:
app: git-sync-demo
spec:
containers:
- name: nginx
image: nginx:1.11-alpine
ports:
- containerPort: 80
volumeMounts:
- name: git-sync-volume
mountPath: /usr/share/nginx
- name: git-sync
image: gcr.io/google_containers/git-sync:v2.0.3
volumeMounts:
- name: git-sync-volume
mountPath: /usr/share/nginx
- name: git-secret
mountPath: /etc/git-secret
env:
- name: GIT_SYNC_REPO
value: git@github.com:apstndb/static-content.git
- name: GIT_SYNC_BRANCH
value: master
- name: GIT_SYNC_ROOT
value: /usr/share/nginx
- name: GIT_SYNC_DEST
value: html
- name: GIT_SYNC_SSH
value: "yes"
volumes:
- name: git-sync-volume
emptyDir: {}
- name: git-secret
secret:
secretName: git-sync-creds
kind: Service
apiVersion: v1
metadata:
name: git-sync-demo
namespace: git-sync
spec:
type: LoadBalancer
selector:
app: git-sync-demo
ports:
- protocol: TCP
port: 80
targetPort: 80
`GIT_SYNC_*` の環境変数と、各ボリュームの定義を見れば大体設定は分かっていただけると思います。
詳しくは [`main.go`](https://github.com/kubernetes/git-sync/blob/master/cmd/git-sync/main.go#L40) に書かれています。
なお、他のコンテナの起動の時点で必ず git-sync の同期が終わっている必要があるなら `GIT_SYNC_ONE_TIME` を設定した git-sync を [init-container](http://kubernetes.io/docs/user-guide/pods/init-container/) で動かすと良いでしょう。
ファイルの定義から Kubernetes 上にリソースを作成します。
$ kubectl apply -f git-sync-demo.yaml
GCE TCP LB が構成されグローバル IP アドレスが取得されるのを待ちます。(IP アドレスは環境によって異なります。)
$ kubectl get service --namespace git-sync --watch
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
git-sync-demo 10.3.247.189 80/TCP 13s
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
git-sync-demo 10.3.247.189 104.199.164.118 80/TCP 1m
`EXTERNAL-IP` に書かれている IP アドレスから git-sync 対象に設定したファイルが読めます。
$ wget http://104.199.164.118/README.md -O -
確認できたファイルを GitHub に対して push などをすると、実際に更新されることが確認できます。
その際の動作中のログは下記のようになります。(Pod 名は実行するごとに変わります。)
$ kubectl logs --namespace git-sync git-sync-demo-832684302-8r3ks git-sync
I1222 08:31:46.542207 1 main.go:162] starting up: ["/git-sync"]
I1222 08:31:48.944960 1 main.go:324] cloned git@github.com:apstndb/static-content.git
I1222 08:31:48.946235 1 main.go:268] syncing to HEAD (5a696ce5f46a3c6c8ee4b14992f13d8077d84719)
I1222 08:31:51.762885 1 main.go:281] added worktree /usr/share/nginx/rev-5a696ce5f46a3c6c8ee4b14992f13d8077d84719 for origin/master
I1222 08:31:51.764538 1 main.go:301] reset worktree /usr/share/nginx/rev-5a696ce5f46a3c6c8ee4b14992f13d8077d84719 to 5a696ce5f46a3c6c8ee4b14992f13d8077d84719
I1222 08:33:57.839532 1 main.go:375] update required
I1222 08:33:57.839556 1 main.go:268] syncing to HEAD (94e6886da482e31fe27b21afb1d6fbd237db99d1)
I1222 08:34:00.202251 1 main.go:281] added worktree /usr/share/nginx/rev-94e6886da482e31fe27b21afb1d6fbd237db99d1 for origin/master
I1222 08:34:00.204707 1 main.go:301] reset worktree /usr/share/nginx/rev-94e6886da482e31fe27b21afb1d6fbd237db99d1 to 94e6886da482e31fe27b21afb1d6fbd237db99d1
なお、ファイルの多いリポジトリで `git pull` をするとワークツリーの更新途中で新しいリビジョンと古いリビジョンのファイルが混ざる不整合な状態になることがありますが、 git-sync ではリビジョンごとに別のパスにチェックアウトした上で、シンボリックリンクをアトミックに張り替えるので不整合な状態にはなりません。