Kubernetes: パッケージマネージャHelm

[Kubernetes Helm](https://github.com/kubernetes/helm)を試してみました。下記は`v2.0.0-alpha.5`で時点での情報です。

# Kubernetes Helmとは

**Kubernetes Helm**(以降**Helm**)とはKubernetesのパッケージマネージャーです。HelmはKubernetesの[SIG-Apps](https://github.com/kubernetes/community/tree/master/sig-apps)で開発がされています。パッケージは**Chart**(s)と呼ばれ、公式でレポジトリ[kubernetes/charts](https://github.com/kubernetes/charts)が用意されています。Kubernetesクラスタに側には**tiller**と呼ばれるデプロイ用のコンポーネントを配置して使用します。

用語 | 意味 | 役割
--- | --- | ---
helm | 船のかじ | yum, aptに相当するパッケージマネージャー。Linux, OSXのバイナリが用意されている
chart | 海図 | deb, rpmに相当するパッケージ。Kubernetesのマニフェストのテンプレートをまとめたもの
tiller | 舵柄 | デプロイを担うサーバーコンポーネント

## 公式レポジトリ

公式レポジトリでは[stable](https://github.com/kubernetes/charts/tree/master/stable)と[incubator](https://github.com/kubernetes/charts/tree/master/incubator)の2つに分けて管理されています。[kubernetes/charts](https://github.com/kubernetes/charts)のstableの説明によると下記のポリシーで運用されています。

- データ永続化の方法が提供される
- アプリケーションのアップグレードがサポートされる
- アプリケーション設定のカスタマイズを許可する
- セキュアなデフォルト設定を持つ
- Kubernetesのアルファ機能を利用しない

公式レポジトリ以外に任意のレポジトリを使用することも可能です。なお、現在は下記のパッケージが提供されています。

### Stable

- [drupal](https://github.com/kubernetes/charts/tree/master/stable/drupal)
- [jenkins](https://github.com/kubernetes/charts/tree/master/stable/jenkins)
- [mariadb](https://github.com/kubernetes/charts/tree/master/stable/mariadb)
- [mysql](https://github.com/kubernetes/charts/tree/master/stable/mysql)
- [redmine](https://github.com/kubernetes/charts/tree/master/stable/redmine)
- [wordpress](https://github.com/kubernetes/charts/tree/master/stable/wordpress)

### Incubator

- [consul](https://github.com/kubernetes/charts/tree/master/incubator/consul)
- [elasticsearch](https://github.com/kubernetes/charts/tree/master/incubator/elasticsearch)
- [etcd](https://github.com/kubernetes/charts/tree/master/incubator/etcd)
- [grafana](https://github.com/kubernetes/charts/tree/master/incubator/grafana)
- [mongodb](https://github.com/kubernetes/charts/tree/master/incubator/mongodb)
- [patroni](https://github.com/kubernetes/charts/tree/master/incubator/patroni)
- [prometheus](https://github.com/kubernetes/charts/tree/master/incubator/prometheus)
- [spark](https://github.com/kubernetes/charts/tree/master/incubator/spark)
- [zookeeper](https://github.com/kubernetes/charts/tree/master/incubator/zookeeper)

## 旧Helm、DeploymentManagerとの関係

Kubernetes Helmは[Helm Classic](https://github.com/helm/helm-classic)(旧Helm)というプロジェクトと、Kubernetesチームが開発するdeployment-manager(dm)という2つのツールが統合されたものです。[Integrate helm and dm #171](https://github.com/kubernetes/helm/issues/171)によると、ハッカソンでこの2つを統合すると決めたらしく、レポジトリ名も"deployment-manager"から"helm"に変更されました。現在のKubernetes Helmも主にDeis(Engine Yard)のエンジニアが開発しています。参考: [Kubernetes HelmのContributors](https://github.com/kubernetes/helm/graphs/contributors)

# Helmを試してみる

## Helmのインストール

Helmのクライアント(`helm`コマンド)のインストールを行います。Homebrew Caskのformulaが用意されてますが、最新のバージョン(`v2.0.0-alpha.5`)を使いたかったので、[releases](https://github.com/kubernetes/helm/releases/tag/v2.0.0-alpha.5)からダウンロードしPATHの通った場所に`helm`バイナリを配置しました。

```bash
$ export HELM_OS=darwin && wget https://storage.googleapis.com/kubernetes-helm/helm-v2.0.0-alpha.5-$HELM_OS-amd64.tar.gz && tar -zxvf helm-v2.0.0-alpha.5-$HELM_OS-amd64.tar.gz && cd $HELM_OS-amd64
```

下記のコマンドでクラスタの初期化を行います。初期化ではkubernetesクラスタにサーバーコンポーネント(`tiller`)のデプロイを行うため、kubectlの接続先設定もされている必要があります。

```bash
$ helm init

Creating /Users/tkusumi/.helm
Creating /Users/tkusumi/.helm/repository
Creating /Users/tkusumi/.helm/repository/cache
Creating /Users/tkusumi/.helm/repository/local
Creating /Users/tkusumi/.helm/repository/repositories.yaml
Creating /Users/tkusumi/.helm/repository/local/index.yaml
$HELM_HOME has been configured at $HOME/.helm.

Tiller (the helm server side component) has been installed into your Kubernetes Cluster.
Happy Helming!
```

下記のようにサーバーコンポーネントの*tiller*がkubernetesクラスタの`kube-system`ネームスペースにデプロイされます。

```bash
$ kubectl get pods --all-namespaces | grep tiller
kube-system   tiller-deploy-637859184-v6za1                         1/1       Running   0          1m
```

`helm version`でクライアント、サーバーのバージョンを確認できます。

```bash
$ helm version
Client: &version.Version{SemVer:"v2.0.0-alpha.5", GitCommit:"36ec1901ebd68ae976fa8ef332ca5ed44598fc24", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.0.0-alpha.5", GitCommit:"4de5c4aa578ed9ca173aff472f00d2167ec59a88", GitTreeState:"dirty"}
```

参考

- https://github.com/kubernetes/helm/blob/master/docs/install.md

## Helmでパッケージをインストールする

`helm search`でstableパッケージの一覧が取得できます。(`2.0.0-Alpha.5`からの機能)

```
$ helm search
NAME                    VERSION DESCRIPTION
stable/drupal           0.3.2   One of the most versatile open source content m...
stable/jenkins          0.1.0   A Jenkins Helm chart for Kubernetes.
stable/mariadb          0.5.0   Chart for MariaDB
stable/mysql            0.1.0   Chart for MySQL
stable/redmine          0.3.1   A flexible project management web application.
stable/wordpress        0.3.0   Web publishing platform for building blogs and ...
```

`helm install PACKAGE_NAME`でパッケージのインストールを行います。ここではJenkinsをインストールしてみます。インストール時にパッケージごとの設定を指定することができます。指定可能な値は各パッケージのREADMEにかかれています。参考: [JenkinsのREADME](https://github.com/kubernetes/charts/blob/master/stable/jenkins/README.md)

```bash
$ helm install stable/jenkins
Fetched stable/jenkins to jenkins-0.1.0.tgz
viable-robin
Last Deployed: Tue Oct 11 13:57:50 2016
Namespace: default
Status: DEPLOYED

~省略~

Notes:
Getting Started:

1. Get your 'admin' user password by running:
  printf $(printf '\%o' `kubectl get secret --namespace default viable-robin-jenkins -o jsonpath="{.data.jenkins-admin-password[*]}"`);echo

2. Get the Jenkins URL to visit by running these commands in the same shell:
**** NOTE: It may take a few minutes for the LoadBalancer IP to be available.                      ****
****       You can watch the status of by running 'kubectl get svc -w viable-robin-jenkins' ****
  export SERVICE_IP=$(kubectl get svc --namespace default viable-robin-jenkins -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP:8080/login

3. Login with the password from step 1 and the username: admin

For more information on running Jenkins on Kubernetes, visit:
https://cloud.google.com/solutions/jenkins-on-container-engine
```

Jenkinsにアクセスする手順が表示されるので手順の通りアクセスし、正しくJenkinsがインストールされたことを確認します。

![image](https://qiita-image-store.s3.amazonaws.com/0/100377/131c2244-dc2f-7d88-0069-01a38605caa4.png)


下記のようにJenkinsのPodが配置されました。

```bash
$ kubectl get pods
NAME                                    READY     STATUS    RESTARTS   AGE
viable-robin-jenkins-4218656479-dvu4v   1/1       Running   0          4m
```

`helm list`コマンドでデプロイされた一覧を確認することができます。

```bash
$ helm list
NAME            REVISION        UPDATED                         STATUS          CHART
viable-robin    1               Tue Oct 11 13:57:50 2016        DEPLOYED        jenkins-0.1.0
```

# パッケージ(Charts)を試しに作ってみる

https://github.com/kubernetes/helm/blob/master/docs/charts.md

ここではサンプルに任意の文字列を設定できるnginxのパッケージ(Charts)を作ります。

`helm create`というコマンドで雛形を生成します。

```
$ helm create helloworld
Creating helloworld
```

下記の構成でファイルが作られます。

```
helloworld/
  Chart.yaml    # chartの情報を記載するyaml
  values.yaml   # chartのデフォルト設定
  charts/       # chartの依存を含めるディレクトリ
  templates/    # マニフェストファイルのテンプレートを配置するディレクトリ
```

`templates/`以下にkubernetesのマニフェストファイルのテンプレートを配置します。ここでは下記のマニフェストのテンプレートを配置しました。テンプレートはGoの[text/template](https://golang.org/pkg/text/template/)形式で記述します。`.Values`というのが任意に設定する情報です。

```yaml:hello-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  # 本来は{{.Release.Name}}などを使ってユニークにする必要がある
  name: "hello-nginx"
  labels:
spec:
  # 任意のレプリカ数を設定できる
  replicas: {{.Values.replicaCount}}
  template:
    metadata:
      labels:
        app: "hello-nginx"
    spec:
      containers:
      - image: nginx:1.11.4
        name: nginx
        ports:
        - containerPort: 80
        command:
        - /bin/bash
        - -c
        # 任意の値をindex.htmlで表示
        - 'echo "<h1>{{.Values.message}}</h1>" > /usr/share/nginx/html/index.html; /usr/sbin/nginx -g "daemon off;"'
```

```hello-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-nginx
  labels:
spec:
  ports:
    - port: 80
      name: http
      targetPort: 80
  selector:
    app: "hello-nginx"
  type: LoadBalancer
```

`values.yaml`に初期値を記載しておきます。

```yaml:values.yaml
replicaCount: 2
message: hello world!
```

`helm lint`という構文チェックツールが付属していおり、構文エラーをチェックすることができます。

```bash
$ helm lint helloworld
==> Linting helloworld
[ERROR] templates/: render error in "helloworld/templates/hello-deployment.yaml": template: helloworld/templates/hello-deployment.yaml:14:24: executing "helloworld/templates/hello-deployment.yaml" at <{{template "fullname...>: template "fullname" not defined

Error: 1 chart(s) linted, 1 chart(s) failed
```

構文に問題がなければパッケージ(`tgz`ファイル)を作成します。

```bash
$ helm package helloworld
```

パッケージされた`helloworld-0.1.0.tgz`が作成されました。下記の用にインストールができます。テンプレートの変数は`--set`フラグでカンマ区切りのkey=value形式、または`--values`フラグでyaml形式で指定することができます。

```bash
$ helm install --set 'message=It works!,replicaCount=3' helloworld-0.1.0.tgz
right-quail
Last Deployed: Tue Oct 11 16:19:10 2016
Namespace: default
Status: DEPLOYED

Resources:
==> extensions/Deployment
NAME          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-nginx   3         3         3            0           0s

==> v1/Service
NAME          CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
hello-nginx   10.187.251.4   <pending>     80/TCP    0s
```

ロードバランサのEXTERNAL-IPを確認してブラウザでアクセスします。

```
$ kubectl get svc hello-nginx
NAME          CLUSTER-IP     EXTERNAL-IP       PORT(S)   AGE
hello-nginx   10.187.251.4   104.155.129.166   80/TCP    53s
```

![image](https://qiita-image-store.s3.amazonaws.com/0/100377/1730135a-f7ef-0c44-1418-747f86fad638.png)

[既存のChart](https://github.com/kubernetes/charts)を見ると、他にもラベルなどの設定がいろいろとあるので、実際に作る際は既存のものを参考にするとよいと思います。例えば`Release.Name`(指定しないとランダムで付けられる)のような[事前に定義された変数](https://github.com/kubernetes/helm/blob/master/docs/charts.md#predefined-values)があり、これらを使ってユニークな`name`を設定したりする必要があります。

## その他にChartでできること

[Chartsのドキュメント](https://github.com/kubernetes/helm/blob/master/docs/charts.md)によると他にChartsで下記のことができるようです。

### 依存関係

Chartの依存関係を`charts/`ディレクトリへのコピーまたは`requirements.yaml`による定義で表現することができます。これにより自動的にChartの依存関係を解決することができます。

```yaml:requirements.yaml
dependencies:
  - name: apache
    version: 1.2.3
    repository: http://example.com/charts
  - name: mysql
    version: 3.2.1
    repository: http://another.example.com/charts
```

### フック

パッケージインストール、削除、アップグレード時にフックする機能が提供されています。例えばシークレットを動的に作ることや、アップグレード前にDBのバックアップを取るなどの用途に利用できます。下記のフックポイントが用意されています。

- pre-install
- post-install
- pre-delete
- post-delete
- pre-upgrade
- post-upgrade

フックの処理はKubernetesのBatch Jobとして定義し、下記のようにアノテーションでフックポイントを指定できます。カンマ区切りで複数していすることもできます。


```yaml
  annotations:
    "helm.sh/hook": post-install
```


# 参考
- https://github.com/kubernetes/helm
- https://github.com/kubernetes/charts
- http://blog.kubernetes.io/2016/10/helm-charts-making-it-simple-to-package-and-deploy-apps-on-kubernetes.html