TECHSTEP

ITインフラ関連の記事を公開してます。

Helm ver.3(ベータ版)を試す

はじめに

Helm-logos

kubernetes上で利用するパッケージ管理ツールにHelmがあります。HelmではChartというパッケージ単位でkubernetesマニフェストを管理し、大量のマニフェストファイルの管理を手助けします。

Helm ver.2まではクライアントサイドのhelm とサーバーサイドのTillerという2つのコンポーネントで構成されていましたが、ver.3からTillerが削除されるという大きな変更が行われました。

今回は新しく発表されたver.3のHelmを簡単に動かしてみます。

Helmとは

Helmはkubernetesマニフェストファイルを管理するパッケージ管理ツールです。kubernetesではマニフェストと呼ばれるファイルを利用してリソースをデプロイすることが多いですが、リソースが増えるにつれて管理する対象のマニフェストファイルも増加し、次第にそれらを管理するのが難しくなります。いわゆる「YAMLの壁」と呼ばれるような状態が生まれてしまいます。YAMLの壁を解決するため様々なパッケージ管理ツールが開発されたが、Helmもそのひとつと言ってよいと思います。

HelmではChartと呼ばれるパッケージ単位でマニフェストファイルを管理します。アプリケーションごとにchartを分けて管理することで、関連する複数のマニフェストファイルを管理し、管理しやすくすることができます。

またChartはリポジトリに格納・公開することで、複数ユーザーで利用できるようにしたり、バージョン管理をすることもできます。リポジトリの場所としてはクラウドストレージやgithub、ローカル環境など様々な環境を利用できます。

※参考リンク:

Helmの概要とChart(チャート)の作り方

Kubernetes YAMLの壁

ver.3からの変更点

ver.3からいくつか大きな変更が行われたHelmについて、まずはHelm公式ページのFAQから変更点を紹介します。

FAQより(和訳+意訳)

Helm Documentation - Frequently Asked Questions

  • Tillerの削除
    • Helmはver.2まではTillerを利用していました。Tillerは複数の共有クラスター上で動作させるうえで重要な役割を担っていました。しかしKubernetes ver.1.6からでRBACがデフォルトで有効になってから、Tillerを本番環境で管理するのが難しくなりました。(Helmを利用する環境には)膨大な数のセキュリティポリシーが存在したため、初めてのユーザーもセキュリティコントロールなしでHelm/Kubernetesを操作できるよう、寛容なセキュリティ設定にしていました。その結果、ユーザーに不要に強い権限を与えることになり、マルチテナントクラスターにTillerをインストールする際には、DevOps、SREは追加オペレーションが必要になっていました。
    • コミュニティ内で意見を集め、調査をした結果、Helm ver.3からは、Tillerを完全に削除することにしました。Tillerを除いたことで、Helmのセキュリティモデルはシンプルになり、Helmの権限はkubeconfigファイルで評価されますクラスター管理者はユーザー権限を操作することができ、リリースの情報はクラスター内に記録され、Helmのその他の機能はそのまま維持しました。
  • upgrade時に3つのステータスを参照する
    • Helm ver.2では、最新のchartマニフェストhelm upgradeで示されたchartマニフェストとを比較し、Kubernetes上のリソースには寧すべき変更点を決定します。Helm ver.2ではkubectl edit等によるクラスター外での変更点は反映されないため、chart上で変更点がない場合、以前の状態にロールバックすることができませんでした。
    • Helm ver.3では、古いchartマニフェスト、現在のリソースの状態、そして新しいchartマニフェストの3つを比較します。そのため、ver.2では反映されなかった変更箇所が、ver.3では反映されるようになります。
  • Release NameはNamespaceにスコープされる
    • tillerの削除に伴い、Releaseに関する情報は、同じNamespaceに保存される。これによりHelm ver.3では、同じ名前のreleaseを2つの異なるNamespaceできるようになりました。
  • Secretリソースがデフォルトの保存ドライバになる
    • Helm ver.2では、ConfigMapsマニフェストがreleaseの情報を保存するのがデフォルトだったが、Helm ver.3ではそれがSecretマニフェストになりました。
  • JSON schemaによるバリデーションチェックの追加
    • ユーザーがhelm install helm upgradeなどのコマンドを実行した際、JSONスキーマによるチェックが行われ、より良いエラーメッセージを表示するようになりました。
  • Chart作成時のrequirement.yaml requirements.lockChart.yaml Chart.lockにそれぞれ移管
  • helm serveの削除
  • Library Chartのサポート
    • Library chartとは、他のchartに共有されますがそれ自身はreleaseに関する生成物を作成しないものです。Library chartはChart.yamlで依存関係を記載することで、他のchartと同様にインストールして利用されます。
  • CLIの名前が一部変更されました。なお古いコマンドも、現在はエイリアスとして利用することができます。
    • helm delete -> helm uninstall
    • helm inspect -> helm show
    • helm fetch -> helm pull

Helm ver.3のインストール(ver.2の削除)

ここから実際にHelm ver.3をダウンロードし、利用してみます。まず利用している環境からver.2のHelmを利用するための設定を削除し、その後Helm ver.3のバイナリファイルをダウンロードします。

# Helm ver.3を利用するためのリソースを削除
# Helm ver.2が存在しない場合は不要な手順
[root@kube-master01 ~]# kubectl delete deployment tiller-deploy -n kube-system
deployment.extensions "tiller-deploy" deleted
[root@kube-master01 ~]# kubectl delete service tiller-deploy -n kube-system
service "tiller-deploy" deleted
[root@kube-master01 ~]# helm version
Client: &version.Version{SemVer:"v2.14.3", GitCommit:"0e7f3b6637f7af8fcfddb3d2941fcc7cbebb0085", GitTreeState:"clean"}
Error: could not find tiller


# Helm ver.3バイナリのダウンロード
[root@kube-master01 ~]# wget https://get.helm.sh/helm-v3.0.0-beta.3-linux-amd64.tar.gz
--2019-09-29 00:05:29--  https://get.helm.sh/helm-v3.0.0-beta.3-linux-amd64.tar.gz
Resolving get.helm.sh (get.helm.sh)... 152.199.39.106, 2606:2800:247:b40:171d:1a2f:2077:f6b
Connecting to get.helm.sh (get.helm.sh)|152.199.39.106|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 13097374 (12M) [application/x-tar]
Saving to: ‘helm-v3.0.0-beta.3-linux-amd64.tar.gz’

100%[==============================================================================>] 13,097,374  20.1MB/s   in 0.6s

2019-09-29 00:05:30 (20.1 MB/s) - ‘helm-v3.0.0-beta.3-linux-amd64.tar.gz’ saved [13097374/13097374]

[root@kube-master01 ~]# tar xzvf helm-v3.0.0-beta.3-linux-amd64.tar.gz
linux-amd64/
linux-amd64/LICENSE
linux-amd64/helm
linux-amd64/README.md
[root@kube-master01 ~]# cd linux-amd64/
[root@kube-master01 linux-amd64]# mv helm /usr/local/bin/
mv: overwrite ‘/usr/local/bin/helm’? y
[root@kube-master01 linux-amd64]# helm version
version.BuildInfo{Version:"v3.0.0-beta.3", GitCommit:"5cb923eecbe80d1ad76399aee234717c11931d9a", GitTreeState:"clean", GoVersion:"go1.12.9"}
[root@kube-master01 linux-amd64]#

以上のようにHelm ver.3をインストールしました。これまではhelm versionコマンドでtillerの有無に関するエラーが表示されましたが、ver.3からは表示されなくなっています。

リポジトリの追加・インストール

次にchartを利用するためにリポジトリを追加し、インストールします。Helm ver.3からはデフォルトでリポジトリが追加されないため、公開されているリポジトリ等を利用するには、まずhelm repo addコマンドで追加する必要があります。

Helm Hub - Discover & launch great Kubernetes-ready apps

# helm search [repo|hub]で検索する
# helm search repoは登録済みリポジトリに含まれるchartを検索する
[root@kube-master01 ~]# helm search repo wordpress
Error: no repositories configured

# helm search hubは公開されているHelm Hubに含まれるchartを検索する
[root@kube-master01 ~]# helm search hub wordpress
URL                                                     CHART VERSION   APP VERSION     DESCRIPTION                     
https://hub.helm.sh/charts/presslabs/wordpress-...      v0.5.1          v0.5.1          Presslabs WordPress Operator Helm Chart
https://hub.helm.sh/charts/presslabs/wordpress-...      v0.5.1          v0.5.1          A Helm chart for deploying a WordPress site on ...
https://hub.helm.sh/charts/bitnami/wordpress            7.3.8           5.2.3           Web publishing platform for building blogs and ...
https://hub.helm.sh/charts/stable/wordpress             7.3.8           5.2.3           Web publishing platform for building blogs and ...
[root@kube-master01 ~]#

Helm ver.2では、helm initで初期化した時点でリポジトリが追加されたため、すぐにchartをインストールできましたが、ver.3ではエラーが表示されます。

# デフォルトでは何のリポジトリも登録されていない
[root@kube-master01 ~]# helm repo list
Error: no repositories to show
[root@kube-master01 ~]# 

そのため、まずhelm repo addコマンドによりリポジトリを追加します。今回はHelm公式のstableリポジトリを追加します。

# helm repo add [NAME] [URL]
[root@kube-master01 ~]# helm repo add stable https://kubernetes-charts.storage.googleapis.com
"stable" has been added to your repositories
[root@kube-master01 ~]# helm repo list
NAME    URL
stable  https://kubernetes-charts.storage.googleapis.com
[root@kube-master01 ~]# helm search repo wordpress
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
stable/wordpress        7.3.8           5.2.3           Web publishing platform for building blogs and ...
[root@kube-master01 ~]#

以上のように追加すると、helm search repoで検索結果が表示されるようになります。次にstableリポジトリを指定してWordpressをインストールします。

# 追加したリポジトリからwordpressをインストール
[root@kube-master01 ~]# helm install wordpress stable/wordpress
NAME: wordpress
LAST DEPLOYED: 2019-10-02 14:18:09.936459344 +0000 UTC m=+2.651313567
NAMESPACE: default
STATUS: deployed
NOTES:
1. Get the WordPress URL:

  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        Watch the status with: 'kubectl get svc --namespace default -w wordpress'
  export SERVICE_IP=$(kubectl get svc --namespace default wordpress --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
  echo "WordPress URL: http://$SERVICE_IP/"
  echo "WordPress Admin URL: http://$SERVICE_IP/admin"

2. Login with the following credentials to see your blog

  echo Username: user
  echo Password: $(kubectl get secret --namespace default wordpress -o jsonpath="{.data.wordpress-password}" | base64 --decode)
[root@kube-master01 ~]# helm list
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART
wordpress       default         1               2019-10-02 14:18:09.936459344 +0000 UTC deployed        wordpress-7.3.8
[root@kube-master01 ~]#

インストールが完了し、helm listwordpressが表示されるようになりました。ちなみにデプロイされたリソースは以下の通りです。

[root@kube-master01 ~]# kubectl get all
NAME                             READY   STATUS    RESTARTS   AGE
pod/wordpress-7f7fb54955-7kknb   0/1     Pending   0          3m39s
pod/wordpress-mariadb-0          0/1     Pending   0          3m39s


NAME                        TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                      AGE
service/kubernetes          ClusterIP      10.96.0.1       <none>          443/TCP                      60d
service/wordpress           LoadBalancer   10.99.246.205   192.168.1.240   80:32589/TCP,443:31718/TCP   3m39s
service/wordpress-mariadb   ClusterIP      10.102.73.2     <none>          3306/TCP                     3m39s


NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/wordpress   0/1     1            0           3m39s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/wordpress-7f7fb54955   1         1         0       3m39s

NAME                                 READY   AGE
statefulset.apps/wordpress-mariadb   0/1     3m39s

[root@kube-master01 ~]#

chartの生成・インストール

次にchartを自前で用意し、それをデプロイします。chartの雛形を生成するにはhelm createコマンドを利用します。ver.2の時と比べ、ディレクトリ構成は多少変化しています。

# chartの生成
[root@kube-master01 ~]# helm create mychart
Creating mychart
[root@kube-master01 ~]

# 生成されたchartのファイル構成を確認
[root@kube-master01 ~]# ls -l mychart/
total 8
-rw-r--r-- 1 root root  905 Sep 29 00:29 Chart.yaml
drwxr-xr-x 2 root root  106 Sep 29 00:29 templates
-rw-r--r-- 1 root root 1060 Sep 29 00:29 values.yaml
[root@kube-master01 ~]# ls -l mychart/templates/
total 20
-rw-r--r-- 1 root root 1329 Sep 29 00:29 deployment.yaml
-rw-r--r-- 1 root root 1415 Sep 29 00:29 _helpers.tpl
-rw-r--r-- 1 root root  896 Sep 29 00:29 ingress.yaml
-rw-r--r-- 1 root root 1292 Sep 29 00:29 NOTES.txt
-rw-r--r-- 1 root root  409 Sep 29 00:29 service.yaml

生成された各ファイルは、それぞれ以下のように利用されます。

ファイル名 内容
Chart.yaml chart地震に関する定義を記載。バージョン情報など
values.yaml templates/配下の各ファイルに入れる変数を指定
templates/deployment.yaml Deploymentマニフェストを定義
templates/_helpers.tpl template/配下で呼び出されるヘルパー関数。各ファイル内でtemplateの後ろに記載する
templates/ingress.yaml Ingressマニフェストを定義
templates/NOTES.txt インストール完了後に表示するメッセージを定義
templates/service.yaml serviceマニフェストを定義

各ファイルの内容は以下の通りです。

Chart.yaml

apiVersion: v2
name: mychart
description: A Helm chart for Kubernetes

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
version: 0.1.0

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application.
appVersion: 1.16.0

values.yaml

# Default values for mychart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

image:
  repository: nginx
  pullPolicy: IfNotPresent

nameOverride: ""
fullnameOverride: ""

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: chart-example.local
      paths: []

  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
  # limits:
  #  cpu: 100m
  #  memory: 128Mi
  # requests:
  #  cpu: 100m
  #  memory: 128Mi

nodeSelector: {}

tolerations: []

affinity: {}

templates/deployment.yaml

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: {{ template "mychart.fullname" . }}
  labels:
{{ include "mychart.labels" . | indent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "mychart.name" . }}
      app.kubernetes.io/instance: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ include "mychart.name" . }}
        app.kubernetes.io/instance: {{ .Release.Name }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
{{ toYaml .Values.resources | indent 12 }}
    {{- with .Values.nodeSelector }}
      nodeSelector:
{{ toYaml . | indent 8 }}
    {{- end }}
    {{- with .Values.affinity }}
      affinity:
{{ toYaml . | indent 8 }}
    {{- end }}
    {{- with .Values.tolerations }}
      tolerations:
{{ toYaml . | indent 8 }}
    {{- end }}

templates/_helpers.tpl

{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "mychart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "mychart.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mychart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Common labels
*/}}
{{- define "mychart.labels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
helm.sh/chart: {{ include "mychart.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}

templates/ingress.yaml

{{- if .Values.ingress.enabled -}}
{{- $fullName := include "mychart.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: {{ $fullName }}
  labels:
{{ include "mychart.labels" . | indent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
{{- if .Values.ingress.tls }}
  tls:
  {{- range .Values.ingress.tls }}
    - hosts:
      {{- range .hosts }}
        - {{ . | quote }}
      {{- end }}
      secretName: {{ .secretName }}
  {{- end }}
{{- end }}
  rules:
  {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
        {{- range .paths }}
          - path: {{ . }}
            backend:
              serviceName: {{ $fullName }}
              servicePort: {{ $svcPort }}
        {{- end }}
  {{- end }}
{{- end }}

templates/NOTES.txt

1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
  {{- range .paths }}
  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
  {{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
  export NODE_PORT=$(kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "mychart.fullname" . }})
  export NODE_IP=$(kubectl get nodes -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
     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 {{ template "mychart.fullname" . }}'
  export SERVICE_IP=$(kubectl get svc {{ template "mychart.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
  export POD_NAME=$(kubectl get pods -l "app={{ template "mychart.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl port-forward $POD_NAME 8080:80
{{- end }}

templates/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: {{ template "mychart.fullname" . }}
  labels:
{{ include "mychart.labels" . | indent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: {{ include "mychart.name" . }}
    app.kubernetes.io/instance: {{ .Release.Name }}

生成したばかりのchartをインストールしてみます。

[root@kube-master01 ~]# helm install mychart ./mychart
NAME: mychart
LAST DEPLOYED: 2019-09-29 01:52:47.183933096 +0000 UTC m=+0.186553085
NAMESPACE: default
STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods -l "app=mychart,release=mychart" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl port-forward $POD_NAME 8080:80

# インストール後確認
[root@kube-master01 ~]# helm list
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART
mychart default         1               2019-09-29 01:52:47.183933096 +0000 UTC deployed        mychart-0.1.0

またインストールしたchartの状態を詳細にみる場合はhelm show [chart名]で確認できます。

helm show mychart

apiVersion: v2
appVersion: 1.16.0
description: A Helm chart for Kubernetes
name: mychart
type: application
version: 0.1.0

---
affinity: {}
fullnameOverride: ""
image:
  pullPolicy: IfNotPresent
  repository: nginx
ingress:
  annotations: {}
  enabled: false
  hosts:
  - host: chart-example.local
    paths: []
  tls: []
nameOverride: ""
nodeSelector: {}
replicaCount: 1
resources: {}
service:
  port: 80
  type: ClusterIP
tolerations: []

---

最後にchartを削除します。

# mychartを削除する
[root@kube-master01 ~]# helm uninstall mychart
release "mychart" uninstalled
[root@kube-master01 ~]# helm ls
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART
[root@kube-master01 ~]#

Local Repositoryにchartを保存する

次にローカルな環境にリポジトリを構築し、作成したchartを保存します。今回はテスト用のため、Chartmuseumの環境をHelmで構築し、先ほど作ったmychartを格納します。そして、helmコマンドで新しいリポジトリ上に格納されたことが確認できるまでを行います。

# chartを検索
[root@kube-master01 ~]# helm search repo chartmuseum
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
stable/chartmuseum      2.3.2           0.8.2           Host your own Helm Chart Repository

# インストール
[root@kube-master01 ~]# helm install chartmuseum stable/chartmuseum
NAME: chartmuseum
LAST DEPLOYED: 2019-10-14 02:39:22.522212778 +0000 UTC m=+2.554448418
NAMESPACE: default
STATUS: deployed
NOTES:
** Please be patient while the chart is being deployed **

Get the ChartMuseum URL by running:

  export POD_NAME=$(kubectl get pods --namespace default -l "app=chartmuseum" -l "release=chartmuseum" -o jsonpath="{.items[0].metadata.name}")
  echo http://127.0.0.1:8080/
  kubectl port-forward $POD_NAME 8080:8080 --namespace default
[root@kube-master01 ~]# helm list
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           
chartmuseum     default         1               2019-10-14 02:39:22.522212778 +0000 UTC deployed        chartmuseum-2.3.2
[root@kube-master01 ~]#

次にインストール時に表示されたコマンドを実行し、chartmuseumリポジトリにアクセスできるようにします。

# Podのデプロイされた場所を確認
[root@kube-master01 ~]# kubectl get pods -o wide
NAME                                       READY   STATUS    RESTARTS   AGE     IP               NODE            NOMINATED NODE   READINESS GATES
chartmuseum-chartmuseum-76d6cbc86c-qnm4w   1/1     Running   0          3m35s   192.168.132.53   kube-worker01   <none>           <none>

# port-forward
[root@kube-master01 ~]# kubectl port-forward chartmuseum-chartmuseum-76d6cbc86c-qnm4w 8080:8080 --namespace default
Forwarding from 127.0.0.1:8080 -> 8080

# 別コンソールを開き、curlコマンドで確認
[root@kube-master01 ~]# curl http://localhost:8080/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to ChartMuseum!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to ChartMuseum!</h1>
<p>If you see this page, the ChartMuseum web server is successfully installed and
working.</p>

<p>For online documentation and support please refer to the
<a href="https://github.com/helm/chartmuseum">GitHub project</a>.<br/>

<p><em>Thank you for using ChartMuseum.</em></p>
</body>
</html>
        [root@kube-master01 ~]#

chartmuseumリポジトリにアクセスできるようになったので、mychartchartを配置する準備をします。chartをリポジトリに配置するのに必要なファイルはchart.tgz index.yamlの2つです。前者はhelm packageコマンドによりchartディレクトリをパッケージングして生成します。

# chartをパッケージング
[root@kube-master01 ~]# ls -l
total 16
-rw-r--r-- 1 3434 3434 11373 Sep  5 20:57 LICENSE
drwxr-xr-x 3 root root    79 Sep 29 00:29 mychart
-rw-r--r-- 1 3434 3434  3309 Sep  5 20:57 README.md
[root@kube-master01 ~]# helm package mychart
Successfully packaged chart and saved it to: /root/mychart-0.1.0.tgz
[root@kube-master01 ~]# ls -l
total 20
-rw-r--r-- 1 3434 3434 11373 Sep  5 20:57 LICENSE
drwxr-xr-x 3 root root    79 Sep 29 00:29 mychart
-rw-r--r-- 1 root root  2334 Oct 14 02:47 mychart-0.1.0.tgz
-rw-r--r-- 1 3434 3434  3309 Sep  5 20:57 README.md
[root@kube-master01 ~]# 

index.yamlを生成するにはhelm repo indexコマンドを利用します。

# index.yamlを生成
[root@kube-master01 ~]# helm repo index ./ --url http://localhost:8080/
[root@kube-master01 ~]# ls -l
total 24
-rw-r--r-- 1 root root   404 Oct 14 02:48 index.yaml
-rw-r--r-- 1 3434 3434 11373 Sep  5 20:57 LICENSE
drwxr-xr-x 3 root root    79 Sep 29 00:29 mychart
-rw-r--r-- 1 root root  2334 Oct 14 02:47 mychart-0.1.0.tgz
-rw-r--r-- 1 3434 3434  3309 Sep  5 20:57 README.md

index.yamlは以下のような内容です。

apiVersion: v1
entries:
  mychart:
  - apiVersion: v2
    appVersion: 1.16.0
    created: "2019-10-14T02:48:03.616046491Z"
    description: A Helm chart for Kubernetes
    digest: ff17768e71be78e0316234183180ff6eddf502466442f62c6b2c69bcb63c894b
    name: mychart
    type: application
    urls:
    - http://localhost:8080/mychart-0.1.0.tgz
    version: 0.1.0
generated: "2019-10-14T02:48:03.615067755Z"

ここまでで準備ができましたので、chartmuseumリポジトリhelm repo addコマンドで新しく登録します。

# 新しいリポジトリを追加する
[root@kube-master01 ~]# helm repo add chartmuseum http://localhost:8080
"chartmuseum" has been added to your repositories
[root@kube-master01 ~]# helm repo list
NAME            URL
stable          https://kubernetes-charts.storage.googleapis.com
chartmuseum     http://localhost:8080
[root@kube-master01 ~]#

この時点では何のchartも格納されていないので、検索しても何も表示されません。

[root@kube-master01 ~]# helm search repo chartmuseum/
No results found
[root@kube-master01 ~]#

先ほど生成したmychart-0.1.0.tgz index.yamlを、リポジトリ格納先に移動します。今回はchartmuseumPodが格納先となりますので、まずはPodの中身を確認し、保存先を調べます。

[root@kube-master01 ~]# kubectl get pods
NAME                                       READY   STATUS    RESTARTS   AGE
chartmuseum-chartmuseum-76d6cbc86c-qnm4w   1/1     Running   0          16m
[root@kube-master01 ~]# kubectl get pods -o yaml

(中略)

  spec:
    containers:
    - args:
      - --port=8080
      - --storage-local-rootdir=/storage ★ここに保存する

(中略)

[root@kube-master01 ~]#

kubectl get pods -o yamlの全出力結果は以下になります。

kubectl get pods -o yaml

apiVersion: v1
items:
- apiVersion: v1
  kind: Pod
  metadata:
    annotations:
      cni.projectcalico.org/podIP: 192.168.132.53/32
    creationTimestamp: "2019-10-14T02:39:22Z"
    generateName: chartmuseum-chartmuseum-76d6cbc86c-
    labels:
      app: chartmuseum
      pod-template-hash: 76d6cbc86c
      release: chartmuseum
    name: chartmuseum-chartmuseum-76d6cbc86c-qnm4w
    namespace: default
    ownerReferences:
    - apiVersion: apps/v1
      blockOwnerDeletion: true
      controller: true
      kind: ReplicaSet
      name: chartmuseum-chartmuseum-76d6cbc86c
      uid: 7f09895a-4329-48b7-b7c2-40ba50107cae
    resourceVersion: "1728741"
    selfLink: /api/v1/namespaces/default/pods/chartmuseum-chartmuseum-76d6cbc86c-qnm4w
    uid: b7610a39-1791-499b-a3b3-5fdf60642b5a
  spec:
    containers:
    - args:
      - --port=8080
      - --storage-local-rootdir=/storage ★ここに保存する
      env:
      - name: CHART_POST_FORM_FIELD_NAME
        value: chart
      - name: DISABLE_API
        value: "true"
      - name: DISABLE_METRICS
        value: "true"
      - name: LOG_JSON
        value: "true"
      - name: PROV_POST_FORM_FIELD_NAME
        value: prov
      - name: STORAGE
        value: local
      image: chartmuseum/chartmuseum:v0.8.2
      imagePullPolicy: IfNotPresent
      livenessProbe:
        failureThreshold: 3
        httpGet:
          path: /health
          port: http
          scheme: HTTP
        initialDelaySeconds: 5
        periodSeconds: 10
        successThreshold: 1
        timeoutSeconds: 1
      name: chartmuseum
      ports:
      - containerPort: 8080
        name: http
        protocol: TCP
      readinessProbe:
        failureThreshold: 3
        httpGet:
          path: /health
          port: http
          scheme: HTTP
        initialDelaySeconds: 5
        periodSeconds: 10
        successThreshold: 1
        timeoutSeconds: 1
      resources: {}
      terminationMessagePath: /dev/termination-log
      terminationMessagePolicy: File
      volumeMounts:
      - mountPath: /storage
        name: storage-volume
      - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
        name: default-token-pvvhs
        readOnly: true
    dnsPolicy: ClusterFirst
    enableServiceLinks: true
    nodeName: kube-worker01
    priority: 0
    restartPolicy: Always
    schedulerName: default-scheduler
    securityContext:
      fsGroup: 1000
    serviceAccount: default
    serviceAccountName: default
    terminationGracePeriodSeconds: 30
    tolerations:
    - effect: NoExecute
      key: node.kubernetes.io/not-ready
      operator: Exists
      tolerationSeconds: 300
    - effect: NoExecute
      key: node.kubernetes.io/unreachable
      operator: Exists
      tolerationSeconds: 300
    volumes:
    - emptyDir: {}
      name: storage-volume
    - name: default-token-pvvhs
      secret:
        defaultMode: 420
        secretName: default-token-pvvhs
  status:
    conditions:
    - lastProbeTime: null
      lastTransitionTime: "2019-10-14T02:39:22Z"
      status: "True"
      type: Initialized
    - lastProbeTime: null
      lastTransitionTime: "2019-10-14T02:39:39Z"
      status: "True"
      type: Ready
    - lastProbeTime: null
      lastTransitionTime: "2019-10-14T02:39:39Z"
      status: "True"
      type: ContainersReady
    - lastProbeTime: null
      lastTransitionTime: "2019-10-14T02:39:22Z"
      status: "True"
      type: PodScheduled
    containerStatuses:
    - containerID: docker://358cac7e3d72c70ce700c96cc072aa1a3a9ac00fccefcb2b49e4cb79ec887617
      image: chartmuseum/chartmuseum:v0.8.2
      imageID: docker-pullable://chartmuseum/chartmuseum@sha256:beecf590c7467eaa2edbf3c977d627cbab8e1aff833318b481a4fe1513342f6e
      lastState: {}
      name: chartmuseum
      ready: true
      restartCount: 0
      state:
        running:
          startedAt: "2019-10-14T02:39:30Z"
    hostIP: 172.16.33.20
    phase: Running
    podIP: 192.168.132.53
    qosClass: BestEffort
    startTime: "2019-10-14T02:39:22Z"
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""
[root@kube-master01 ~]# 

格納先がわかったのでファイルを移動します。今回はkubectl cpコマンドを利用し、ローカルのファイルをコンテナ内にコピーします。

# kubectl cp
[root@kube-master01 ~]# kubectl cp index.yaml chartmuseum-chartmuseum-76d6cbc86c-qnm4w:/storage/
[root@kube-master01 ~]# kubectl cp mychart-0.1.0.tgz chartmuseum-chartmuseum-76d6cbc86c-qnm4w:/storage/

# コピー結果の確認
[root@kube-master01 ~]# kubectl exec -it chartmuseum-chartmuseum-76d6cbc86c-qnm4w -- ls -l /storage/
total 8
-rw-r--r--    1 chartmus chartmus       404 Oct 14 02:48 index.yaml
-rw-r--r--    1 chartmus chartmus      2334 Oct 14 02:47 mychart-0.1.0.tgz
[root@kube-master01 ~]#

これでファイルを配置できました。ただこのままhelm searchコマンドを利用しても何も表示されないので、helm repo updateコマンドでリポジトリを更新する必要があります。

# ファイルの移動直後は検索しても表示されない
[root@kube-master01 ~]# helm search repo chartmuseum/
No results found
[root@kube-master01 ~]#

# リポジトリをアップデート
[root@kube-master01 ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "chartmuseum" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈
[root@kube-master01 ~]#

# アップデート後の確認
[root@kube-master01 ~]# helm search repo chartmuseum/
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
chartmuseum/mychart     0.1.0           1.16.0          A Helm chart for Kubernetes
[root@kube-master01 ~]#

以上の通りchartmuseum/mychartが確認できるようになりました。ここまでくればhelm installコマンドでchartをインストールすることができるようになります。

# chartmuseumリポジトリからインストール
[root@kube-master01 ~]# helm install mychart2 chartmuseum/mychart
NAME: mychart2
LAST DEPLOYED: 2019-10-14 10:23:25.577755362 +0000 UTC m=+0.101634524
NAMESPACE: default
STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods -l "app=mychart,release=mychart2" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl port-forward $POD_NAME 8080:80

# インストール後確認
[root@kube-master01 ~]# helm list
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART          
chartmuseum     default         1               2019-10-14 02:39:22.522212778 +0000 UTC deployed        chartmuseum-2.3.2
mychart2        default         1               2019-10-14 10:23:25.577755362 +0000 UTC deployed        mychart-0.1.0  
[root@kube-master01 ~]#

最後に

今回はHelm ver.3を操作してみました。tillerが削除されたり一部コマンドが変更されたりと、ver.2からの変更点がいくつかありますが、簡単に操作する分にはそこまで抵抗は感じませんでした。現時点ではまだベータ版しか利用できないため、本番環境で利用することは推奨できませんが、セキュリティ的に向上したことを考えれば、本番環境ではver.3へと推移する可能性が高いと思われます。そのため今のうちに使い方を見ておいたほうが良いかもしれません。

またLibrary Chartなど、Helm ver.3からの新機能については今回触れなかったので、また別の機会で紹介したいと思います。

参考リンク

Helm公式ページ

Helmの概要とChart(チャート)の作り方

Kubernetes YAMLの壁

Helm Documentation - Frequently Asked Questions

Helm Documentation - Migrating Helm v2 to v3

Qiita - Helm v3.0.0-alphaの試し方

Helm Hub - Discover & launch great Kubernetes-ready apps

How to create a public Helm Repo using Azure Storage (and it works with Helm3 beta3)

Chartmuseum公式ページ