zlabjp/kubernetes-resource: Kubernetes を操作する Concourse リソース

## 概要

ゼットラボではいくつかの CI/CD ツールを利用していますが、その中の一つが Concourse です。Concourse はパイプラインベースの CI/CD ツールで Pivotal により開発されています。Concourse から Kubernetes にアプリケーションをデプロイするための Concourse リソースに我々の要件を満たすものが存在しなかったため、新たに開発し OSS (MIT License) として公開しています。

- [zlabjp/kubernetes\-resource: A Concourse resource for controlling the Kubernetes cluster](https://github.com/zlabjp/kubernetes-resource)

## Kubernetes にどのようにアプリケーションをデプロイするか

Kubernetes にアプリケーションをデプロイするには Kubernetes を操作するための CLI である kubectl を利用します。

- kubectl run: 特定のコンテナイメージをクラスタで実行する
- kubectl create: リソースを作成する（既に作成済の場合は実行に失敗する）
- kubectl delete: リソースを削除する
- kubectl apply: リソースに設定を適応する
- kubectl get: 一つまたは複数のリソースの情報を表示する

この他にも多くのサブコマンドが用意されており、これらを組み合わせてアプリケーションをデプロイします。Kubernetes はアプリケーションの構成を Pod, Service, Volume など複数の「リソース」を組み合わせて表現します。そのため、デプロイというのはリソースを操作することと同義です。

これらのサブコマンドを駆使してリソースを管理する手法を3つ紹介します。

### Imperative command

Imperative command はおもに `kubectl run` などの一度限りのコマンドを利用してアプリケーションをデプロイする方法です。以下の例は、`nginx:latest` イメージをレプリカ数5で myapp という名前で作成します。この手法はアプリケーションの設定を詳細に設定できないため、開発用途など一時的に実行したい場合に利用します。

```bash
# Start a replicated contianer of nginx
$ kubectl run myapp --image=nginx:latest --replicas=5
```

### Imperative object configuration

Imperative object configuration はリソースの設定をマニフェストファイルとして YAML または JSON としてファイルに記載し、`kubectl create` や `kubectl delete` などを利用してリソースを作成、削除します。この方法は Imperative command と比較してアプリケーションの設定をファイルとして記載するため、バージョン管理することができます。ただしこれらのコマンドはリソースが既に作成されていたり、削除されていて存在しない場合に実行に失敗します。

```bash
# Create a resource by a manifest file
$ kubectl create -f nginx-deployment.yaml
```

### Declarative object configuration

Declarative object configuration をマニフェストファイルを `kubectl apply` のみを利用してリソースを作成、削除します。`kubectl apply` は `kubectl create` と比較して既にリソースが存在していればそれを更新し、存在していなければ作成します。また更新方法も他のコマンドによるリソースの操作に干渉しないものとなっており、CD としてアプリケーションを継続的にデプロイするにはもっとも適しています。

```bash
# Create/Update a resource by a manifest file
$ kubectl apply -f nginx-deployment.yaml
```

`kubectl apply` の挙動についてより詳しく知りたいなら [kubectl apply の仕組み / How kubectl apply works](https://speakerdeck.com/tksm/how-kubectl-apply-works) を参照してください。

## Concourse で Kubernetes にアプリケーションを継続的にデプロイする

Concourse から Kubernetes を操作するには [zlabjp/kubernetes-resource](https://github.com/zlabjp/kubernetes-resource) を利用できます。これは Concourse リソースとして動作し、クラスタに対して kubectl を使った任意のコマンドを実行できます。またアプリケーションをデプロイする上で便利な機能を備えています。

### Versions

このリソースは下記のような複数のバージョンを提供しています。バージョンは kubectl のバージョンと紐付いているため、Kubernetes クラスタのバージョンと同一のバージョンを利用することを推奨します。

 - `zlabjp/kubernetes-resource:1.8` ([stable-1.8](https://storage.googleapis.com/kubernetes-release/release/stable-1.8.txt))
 - `zlabjp/kubernetes-resource:1.7` ([stable-1.7](https://storage.googleapis.com/kubernetes-release/release/stable-1.7.txt))
 - `zlabjp/kubernetes-resource:1.6` ([stable-1.6](https://storage.googleapis.com/kubernetes-release/release/stable-1.6.txt))
 - `zlabjp/kubernetes-resource:latest` ([latest](https://storage.googleapis.com/kubernetes-release/release/latest.txt))

### ソース設定

kubeconfig ファイルを直接渡す方法とクラスタコンフィグを個々に設定する方法の二つを提供します。

#### kubeconfig

- `kubeconfig`: *任意.* kubeconfig ファイル
    ```yaml
    kubeconfig: |
      apiVersion: v1
      clusters:
      - cluster:
        ...
    ```

kubeconfig ファイルの生成には [zlabjp/kubernetes-scripts](https://github.com/zlabjp/kubernetes-scripts) に含まれる `create-kubeconfig` スクリプトを利用すると便利です。

```bash
# Create a kubeconfig to access the apiserver with the specified serviceaccount and outputs it to stdout.
$ ./create-kubeconfig <serviceaccount-name> <kubectl-options>
```

#### クラスタ設定

- `server`: *任意.* API サーバとアドレスとポート。`token` が必須です。 (e.g. `https://192.168.99.100:8443`
- `token`: *任意.* API サーバの認証に利用するトークン。`server` が必須です。 (e.g. `eyj....`
- `namespace`: *任意.* ネームスペース。デフォルトは `default` です。
- `certificate_authority`: *任意.* 認証局の証明書です。
- `insecure_skip_tls_verify`: *任意.* もし `true` なら、API サーバの証明書の有効性を確認しません。HTTPS が安全ではなくなります。デフォルトは `false` です。

### 動作

out のみの実装です。

#### `check`: 何もしない

#### `in`: 何もしない

#### `out`: Kubernetes クラスタを操作する

`kubectl apply` や `kubectl delete`, `kubectl label` などのような Kubernetes クラスタの操作を行う。

##### パラメータ

- `kubectl`: *必須.* `kubectl` に続く任意のコマンドを指定します。(e.g. `apply -f ...`
- `wait_until_ready`: *任意.* 全ての Pods が Ready になるまで待機する秒数です。0 なら待機しません。デフォルトは `30` です。
- `wait_until_ready_interval`: *任意.* 全ての Pods が Ready かどうか確認する間隔（秒）です。デフォルトは `3` です。
- `wait_until_ready_selector`: *任意*. Ready かどうか確認する Pods を絞り込む[ラベルセレクタ](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors)です。デフォルトはネームスペース内の全ての Pods です。

Kubernetes は非同期の分散システムのため、リソースの操作はスペックさえ満たしていればコマンドとしては成功しますが、実際には例えばコンテナイメージのバージョンが存在しないものが指定されている場合などデプロイに失敗している場合があります。そのような場合にジョブを失敗させるために `wait_until_ready` を用意しています。これはそのネームスペースの Pod が Running になるまで待機します。もし指定された時間内に Pod が全て Running にならなければジョブが失敗します。対象の Pod を絞り込むには `wait_until_ready_selector` を利用してください。またこの機能によってデプロイ後に e2e テストを実施する場合にコンテナが起動する前にテストを実行してしまうことを防ぐことができます。

### パイプライン例

`repo` リポジトリに含まれるマニフェストファイルの更新をトリガに `prod` クラスタに対して `kubectl apply` を実行しています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3780/98544cc7-ec4c-e0db-cff2-786938cd9b32.png)

```yaml
resource_types:
- name: kubernetes
  type: docker-image
  source:
    repository: zlabjp/kubernetes-resource
    tag: "1.8"
resources:
- name: prod
  type: kubernetes
  source:
    kubeconfig: ((prod-kubeconfig))
- name: repo
  type: git
  source:
    ...
jobs:
- name: prod-deploy
  plan:
  - get: repo
    trigger: true
  - put: prod
    params:
      kubectl: apply -f repo/deploy -f repo/deploy/prod
      wait_until_ready_selector: app=myapp
```

`kubectl` パラメータはコマンド置換に対応しているため、下記のような使い方もできます。

```yaml
jobs:
- name: force-update-deployment
  serial: true
  plan:
  - put: mycluster
    params:
      kubectl: |
        patch deploy nginx -p '{"spec":{"template":{"metadata":{"labels":{"updated_at":"'$(date +%s)'"}}}}}'
      wait_until_ready_selector: run=nginx
```

## まとめ

[zlabjp/kubernetes\-resource](https://github.com/zlabjp/kubernetes-resource) は Kubernetes クラスタを操作する Concourse リソースです。kubectl のさまざまなサブコマンドを駆使してクラスタを操作することができます。下記リポジトリにこのリソースを利用したデモのパイプラインを用意しています。こちらも参考にしてください。

- https://github.com/zlabjp/concourse-meetup-tokyo-7-demo

## おわりに

このエントリは、弊社 Z Lab のメンバーによる [Z Lab Advent Calendar 2017](https://qiita.com/advent-calendar/2017/zlab) の一日目として業務時間中に書きました。二日目は @Ladicle による Istio 入門です。

## 参考

- [Kubernetes Object Management \| Kubernetes](https://kubernetes.io/docs/tutorials/object-management-kubectl/object-management/)
- [Continuous Delivery with Concourse and Kubernetes // Speaker Deck](https://speakerdeck.com/superbrothers/continuous-delivery-with-concourse-and-kubernetes)
- https://github.com/zlabjp/concourse-meetup-tokyo-7-demo