NetApp Digital Transformation Lab (NDX)

https://travis-ci.org/NetAppJpTechTeam/NetAppDigitalTransformationLab.svg?branch=master

はじめに

目的

  • デジタルトランスフォーメーションを支える根幹技術とNetApp製品を組み合わせることによる価値を触りながら体験
  • 特定のシナリオに沿った関連技術の組み合わせ検証
  • Labを通じての技術者コミュニティの創出

連絡方法

連絡先は以下を準備しています。

Contents

基礎編

コンテナ化のシナリオを実施する

基礎編:はじめに

概要

既存のアプリケーションをクラウドネイティブ環境に移し替え、 クラウドネイティブなアーキテクチャに変えていく体験の場です。

流れ

Level0〜Level2までは通しで実施することをおすすめします。

  • Level0:環境確認
  • Level1:既存アプリケーションのコンテナ化
    • ガイド上はWordPressをサンプルとして記載しています。
  • Level2:コンテナ化したアプリケーションのデータ永続化
    • DynamicStorage Provisioningの体験
    • NetApp Trident の導入やStorageClassを作成
    • アプリケーションのデータ永続化を実施
    • Trident 特有の機能を使ってみる

Level3 からは各自の役割や使いそうなものをピックアップして実施いただくことをおすすめしています。

  • Level3:コンテナ化したアプリケーションのデータ永続化
    • CI/CDツールをHelmで導入
    • CI/CDパイプラインを作成
    • Blue/Green, Canaryリリースの導入
  • Level4: 運用
    • アプリケーションの可用性を向上させる
    • Kubernetes クラスタの操作性を向上させる
    • インフラの可用性を向上させる
    • アプリケーションスタック、Kubernetes スタックの管理
  • Level5: マイクロサービス化
    • アプリケーションの可用性を向上させる
    • Kubernetes クラスタの操作性を向上させる
    • インフラの可用性を向上させる
    • アプリケーションスタック、Kubernetes スタックの管理

Level 0: 環境の確認・基本操作

目的・ゴール: ラボを実施する環境の確認

本ラボではkubernetesクラスタへの接続確認と稼働確認を行うことが目的です。

ガイドの中では以下を確認しています。

  • ラボを実施する環境の構成理解
  • 環境への接続確認
  • kubernetesの基本操作を確認
流れ
  1. ユーザIDの確認
  2. 環境へログイン
  3. 基本コマンド確認、k8s へアプリケーションデプロイ
kubernetes環境へのログイン

各自配布されている接続先情報にログイン出来るかを確認してください。

kubernetesにデプロイ
kubernetes基本操作

必要となるコマンドラインツールがインストールされていることを確認します。

$ kubectl version --short

Client Version: v1.15.X
Server Version: v1.15.X

Client(kubectl)と Server(cluster)で同じ Versionが動いている事を確認してください。

次にクラスタを形成するノードを確認します。

$ kubectl get nodes

NAME     STATUS   ROLES    AGE     VERSION
master   Ready    master   3h56m   v1.15.3
node0    Ready    <none>   3h56m   v1.15.3
node1    Ready    <none>   3h55m   v1.15.3
node2    Ready    <none>   3h55m   v1.15.3

STATUSが Readyになっている事を確認してください。 ※ Labのリソースによりノード数は増減する場合があります。

デプロイメント

kubernetesクラスタに作成したコンテナアプリケーションをデプロイするためには 「Deployment」を作成します。 kubectlを使用して、アプリケーションをデプロイします。

以下では kubectl run を実行すると「Deployment」が作成されます。

$ kubectl run 任意のデプロイメント名 --image=nginx --port=80

deployment "nginxweb" created

デプロイが完了したら以下のコマンドで状況を確認します。

$ kubectl get deployments

NAME                                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginxweb                              1         1         1            1           53s

デプロイしたアプリケーションのサービスを確認します。 まだこの状態ではデプロイしたアプリケーションのサービスは存在しない状況です。

$ kubectl get services

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   8s
外部向けに公開

外部向けにサービスを公開します。 公開後、再度サービスを確認します。

$ kubectl expose deployment/上記のデプロイメント名 --type="NodePort" --port 80

service "nginxweb" exposed

kubectl expose コマンドで外部へ公開しました。

サービス一覧から公開されたポートを確認します。

$ kubectl get services

NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP        5d
nginxweb     NodePort    10.103.136.206   <none>        80:30606/TCP   1m

PORT 列を確認します。上の実行例でいうと「30606」ポートの部分を確認します。

--type="NodePort" を指定すると各ノード上にアプリケーションにアクセスするポート(標準で30000–32767)を作成します。 ノードにアクセスしポッドが動いていれば、そのままアクセスします。 ノードにポッドがなければ適切なノード転送される仕組みを持っています。 そのためマスターノードにアクセスすればk8sが適切に転送するという動作をします。

ホストのIPを確認します。

$ ifconfig -a | grep 192.168.*

  inet addr:192.168.10.10  Bcast:192.168.10.255  Mask:255.255.255.0

上記の情報を元にIPを生成してアクセスします。

  • http://確認したIP:確認したポート番号/

アクセス時に以下の画面が表示されれば稼働確認完了です。

_images/nginx.png

状態を確認します。

$ kubectl describe deployment nginxweb

Name:                   nginxweb
Namespace:              default
CreationTimestamp:      Tue, 20 Mar 2018 13:44:08 +0900
Labels:                 run=nginxweb
Annotations:            deployment.kubernetes.io/revision=1
Selector:               run=nginxweb
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  1 max unavailable, 1 max surge
Pod Template:
  Labels:  run=nginxweb
  Containers:
   nginxweb:
    Image:        nginx
    Port:         80/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginxweb-78547ccd78 (1/1 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  15m   deployment-controller  Scaled up replica set nginxweb-78547ccd78 to 1

Replicas の項目で 1 available となっていればデプロイメント成功です。

問題発生時のログの確認方法

デプロイに失敗するようであれば以下のコマンドで状態を確認します。

ポッドの状態を確認するコマンド

$ kubectl logs ポッド名

デプロイメントの状態を確認するコマンド

$ kubectl describe deployments デプロイメント名

他にも以下のようなコマンドで状態を確認することができます。 デプロイ時のYAMLファイル単位や、定義しているラベル単位でも情報を確認できます。

$ kubectl describe -f YAML定義ファイル
$ kubectl describe -l ラベル名

よく使うコマンドや問題発生時の確認方法については次のページにまとめました。 今後のラボでうまくいかない場合いはぜひ参考にしてください。

コマンドリファレンス

クリーンアップ

コマンドラインの操作は完了です。 今までデプロイしたアプリケーションを削除します。

$ kubectl delete deployments デプロイメント名
$ kubectl delete services サービス名
まとめ

このラボではこの先のラボを行うための基本となる操作及び環境の確認を実施しました。

この先は各自ガイドを見ながら進めてください。

ここまでで Level0 は終了です。

Level 1: アプリケーションをコンテナ化する

目的・ゴール: アプリケーションをコンテナ化する

今回はコンテナに適したアーキテクチャへ変更するまえの段階として、 オンプレミスで仮想マシンで動いているアプリケーションについてコンテナ化をしていきます。

コンテナ技術のDockerを使うことでクラウド、オンプレミス、PCなどどのような環境でもアプリケーションを稼働させることができます。

このレベルではラボで使用するKubernetes上で稼働させるアプリケーションのコンテナイメージを作成しデプロイするのが目標です。

流れ
  1. (Optional) Dockerfileを作成する。
  2. (Optional) ビルドを行いDockerイメージを作成
  3. (Optional) 作成したDockerイメージをイメージレジストリに登録
  4. アプリケーションのマニフェストファイルを作成、イメージレジストリに登録したイメージを使用
  5. アプリケーションをKubernetes上へデプロイ、稼働確認
コンテナ化の準備

本ラボでは以下のミドルウェアやスタックを使ったアプリケーションを想定しています。 基本的にはアプリケーションをコンテナ化する際にはDockerHubで作成済みのイメージを使用することで効率よくコンテナ化することができます。

Web/AP レイヤー

  • nginx
  • apache
  • tomcat

Databaseレイヤー

  • mySQL
  • Postgress
  • Oracle
  • MongoDB
コンテナイメージの作成
このステップはアプリケーションを持ち込みの場合や複雑なスタックをコンテナ化する際に行うステップです。
選択したアプリケーションによっては不要なステップになるのでやるべきかどうかを確認してください。

その場合、 アプリケーションのマニフェストファイルを作成してデプロイ からはじめてください。

想定するアプリケーションのコンテナイメージを作成します。

Dockerfile のリファレンス Dockerfile Reference ファイル

留意点としては以下の通りです。

  • アプリケーションの配置をDockerfile内に配置

  • 基本となるコンテナイメージについてはDockerHubで探してベースイメージとする

  • 静的な構成となっていないか(IPパスワードのべた書きなど)

    • 環境変数で設定出来るよう設計する。のちほどk8sのSecretなどでパスワードを保存
  • 冪等性はコンテナイメージ側で対応する。責任範囲を明確にしてイメージを作成

  • ステートフルなものについてはコンテナに適したものにする

ヒント

記述例を提示します。このままビルドしてもイメージは作成されませんのであくまで記述例としてみてください。 どうしても進まない場合は サンプル: Dockerfile記述例 をクリックしてください。

コンテナイメージのビルド

作成した Dockerfileをビルドしてイメージを作成します。

バージョニングを意識してコンテナイメージを作成します、コンテナイメージに明示的にバージョンを指定します。

$ docker build -t 生成するコンテナイメージ名:タグ名 Dockerファイルのパス

Dockerイメージの生成方法は複数の手法があります。 例えば、普通のOSイメージを起動して、ログインしパッケージなどのインストールを行っていく手法があります。 メリットとしてはオペレーションで作成したものをイメージとして登録できるため、Dockerfileを作成しなくても良いといメリットがある一方で、 コンテナイメージの作成方法が不透明となる可能性もあります。

イメージレジストリに登録

プライベートレジストリ、DockerHubは選択いただけます。 このラボで作成したイメージを自社などで再利用したい場合はDockerHubにpushすることもできます。

DockerHub へログイン

DockerHubにアカウントがあることが前提です。

$ docker login

  ユーザ名、パスワードを入力

$ docker image push アカウント名/コンテナイメージ名:タグ名
Private registry (Harbor) を使う場合

private registry を使う場合はラボ内にHarborを準備しています。

Harbor URL: https://registry.ndxlab.net/

本ラボではプロジェクトを事前準備しており、コンテナイメージのレジストリとして使用できます。 Harborへログインしてみましょう。上記のURLにアクセスするとログイン画面が開きます。

_images/harbor-login.png

ログイン・パスワードは以下のものを利用ください。

  • user: user[ユーザ環境番号]
  • pass: Netapp1!

ログインするとプロジェクト一覧が表示されます。

_images/harbor-project-list.png

自身のユーザ名をクリックすると現在登録されているイメージが参照できます。 また同画面の「PUSH IMAGE」をクリックするとイメージのタグ付、レジストリへのpushの手順が提示されます。

_images/harbor-push-image.png

Dockerイメージのビルド、pushのサンプルは以下の通りです。

$ docker login https://registry.ndxlab.net
$ docker tag  pets_object_detection  registry.ndxlab.net/user[ユーザ環境番号]/pets_object_detection:1.0
$ docker push registry.ndxlab.net/user[ユーザ環境番号]/pets_object_detection:1.0
アプリケーションのマニフェストファイルを作成してデプロイ

Level 0: 環境の確認・基本操作 ではコマンドラインで作成してきましたがYAMLファイルで1サービスをまとめてデプロイ出来るようにします。

ファイルのセクション構成としては以下の通りです。

  • Service
  • PersistentVolumeClaim
  • Deployment

サンプルファイルを準備しましたのでそれぞれの項目の意味を考え作成してみましょう。

(https://kubernetes.io/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/ を参考としています。)

ここではサンプルとしてWordPressとMySQLをデプロイします。 MySQLではSecretオブジェクトを使用しパスワードを渡すようになっています。

流れとしては、以下の3つを実施します。

どの部分を実施しているかを把握しながらすすめましょう。

  1. MySQL 用のSecretオブジェクトを作成
  2. MySQL をデプロイ
  3. WordPressをデプロイ
Secretの作成

ここではKubernetes上でパスワードを受け渡すときなどに使う、Secretを作成します。

Secretの説明はこちらです。

$ kubectl create secret generic mysql-pass --from-literal=password=YOUR_PASSWORD

作成後は以下のコマンドで結果を確認します。

$ kubectl get secrets

     NAME                  TYPE                    DATA      AGE
      mysql-pass            Opaque                  1         42s
MySQLのデプロイ

mysql-pass という名前でSecretができたのでそのSecretを使ってMySQLを起動します。

アプリケーションをデプロイするマニフェストファイルの例 mysql-deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
        - image: mysql:5.6
          name: mysql
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-pass
                  key: password
          ports:
            - containerPort: 3306
              name: mysql

上記のマニフェストをもとにDeploymentを作成します。

kubectl create -f mysql-deployment.yaml

少々時間がかかるのでどのように状態が移って行くか確認し、「Status」が「Running」になることを確認してください。

$ kubectl get pods

NAME                               READY     STATUS    RESTARTS   AGE
wordpress-mysql-1894417608-x5dzt   1/1       Running   0          40s
WordPressのデプロイ

MySQLのコンテナが立ち上がったらそのMySQLに接続するWordPressをデプロイします。

アプリケーションをデプロイするマニフェストファイルの例 wordpress-deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: frontend
  type: LoadBalancer
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
        - image: wordpress:4.8-apache
          name: wordpress
          env:
            - name: WORDPRESS_DB_HOST
              value: wordpress-mysql
            - name: WORDPRESS_DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-pass
                  key: password
          ports:
            - containerPort: 80
              name: wordpress

MySQLと同様にデプロイします。

kubectl create -f wordpress-deployment.yaml
kubectlの操作を容易にする

上記のマニフェストにも記載がありますが、Labelには複数の使い方があります。 Serviceが接続先を見つけるために使っている例が上記のマニフェストとなります。

kubectlのオペレーションの簡易化のためlabelをつけることをおすすめします。 例えば以下のような使い方があります。

kubectl get pods -l app=nginx などのようにlabelがついているPod一覧を取得といったことが簡単にできます。 ほかにも以下の様なことが可能となります。

  • kubectl delete deployment -l app=app_label
  • kubectl delete service -l app=app_label
  • kubectl delete pvc -l app=wordpress
アプリケーションの稼働確認

デプロイしたアプリケーションにアクセスし正常稼働しているか確認します。

アクセスするIPについてはサービスを取得して確認します。

$ kubectl get svc

結果として以下のような出力が得られます。

今回はService.typeをLoadBalancerで指定しているため、EXTERNAL-IP欄に表示されたIPでアクセスしてみましょう。

NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes        ClusterIP   10.96.0.1      <none>        443/TCP        6d
wordpress         LoadBalancer    10.98.247.58   192.168.10.210        80:32048/TCP   2h
wordpress-mysql   ClusterIP   None           <none>        3306/TCP       2h
  • 今回はオンプレミスでMetalLBを使用しLoadBalancerでExternal-IPを使用できるようにしました。

Service.Type=NodePortについても確認しましょう。

注釈

kubectl引数の省略系について

今回はServiceの確認をする際に svc という省略形でコマンドを実行しました。 他のオブジェクトも同様に省略形があります。コマンド入力を省力化したい場合は省略形も使ってみましょう。

kubectl --helpkubectl XX --help コマンドで確認できます。

まとめ

kubectlやYAMLで記載するマニフェストファイルを使ってk8sへのデプロイが体感できたかと思います。 実運用になるとこのYAMLをたくさん書くことは負荷になることもあるかもしれません.

その解決のためにパッケージマネージャーHelm 等を使ってデプロイすることが多いかと思います。 このラボでは仕組みを理解していただき、応用出来ることを目的としています。

ここまでで Level1 は終了です。

Level 2: ステートフルコンテナの実現

目的・ゴール: アプリケーションのデータ永続化を実現

アプリケーションは永続化領域がないとデータの保存ができません。 KubernetesではStatic provisioningとDynamic provisioningの2つの永続化の手法があります。

このレベルではDynamic provisioningを実現するためDynamic provisionerであるTridentをインストールし、 マニフェストファイルを作成しデータの永続化をすることが目標です。

流れ
  1. Dynamic storage provisioningを実現(Tridentのインストール)

  2. StorageClassの作成

  3. PVCをkubernetesマニフェストファイルに追加

    1. 作成したStorageClassを使用する
    2. PVCをkubernetesにリクエストした時点で動的にストレージがプロビジョニングされる
  4. アプリケーションを稼働させて永続化ができていることを確認

コンテナでの永続データのカテゴライズ

コンテナ化されたアプリケーション、環境での永続データは 以下のように分類して考え必要な物をリストアップしました。

  • データベースのデータファイル、ログファイル
  • 各サーバのログファイル
  • 設定ファイル
  • 共有ファイル
Dynamic provisioning

ステートフルコンテナを実現する上でストレージは重要なコンポーネントになります。

Dynamic volume provisiong はオンデマンドにストレージをプロビジョニングするためのものです。

Static provisioning、Dynamic provisioning それぞれを比較します。

Static provisioningの場合、クラスタの管理者がストレージをプロビジョニングして、PersitentVolumeオブジェクトを作成しkubernetesに公開する必要があります。

Dynamic provisioningの場合、Static provisioningで手動で行っていたステップを自動化し、管理者がおこなっていたストレージの事前のプロビジョニング作業をなくすことができます。

StorageClassオブジェクトで指定したプロビジョナを使用し、動的にストレージリソースをプロビジョニングすることができます。

StorageClassには様々なパラメータを指定することができアプリケーションに適したストレージカタログ、プロファイルを作成することができ、物理的なストレージを抽象化するレイヤとなります。

Dynamic Provisioningを実現するために ストレージを制御する Provisioner が必要になります。その標準的なインターフェースとして 2019/1からContainer Storage InterfaceがGAになり、 Kubernetes 1.14からは CSI 1.1がサポートされています。

ネットアップはDynamic provisioningを実現するためのNetApp Tridentというprovisionerを提供しています。

Tridentは CSIを使わない従来同様のTridentと CSIを使う CSI Tridentが提供されていますが、 19.07からは CSI Tridentがデフォルトでインストールされるようになりました。

このレベルではTridentでDynamic provisioningを行い、アプリケーションのデータ永続化を実現します。

NetApp Tridentのインストール

Dynamic storage provisioningを実現するためNetApp Tridentを導入します。 TridentはPodとしてデプロイされ通常のアプリケーションと同様に稼働します。

インストール事前準備

Trident のインストールでk8sクラスタの管理者権限が必要になります。

$ kubectl auth can-i '*' '*' --all-namespaces

バックエンドに登録するストレージのマネジメントIP(配布資料のsvmXXのIPアドレス)にk8sクラスタのコンテナから疎通が取れるかを確認します。

$ kubectl run -i --tty ping --image=busybox --restart=Never --rm --  ping [マネジメントIP]
Tridentインストール(19.07〜)

バイナリをダウンロードしてインストールします。(例はバージョン19.07.0)

$ wget https://github.com/NetApp/trident/releases/download/v19.07.0/trident-installer-19.07.0.tar.gz

$ tar -xf trident-installer-19.07.0.tar.gz

$ cd trident-installer

Tridentの制御にはtridentctlを使います。

tridentctl ユーティリティではドライランモードとデバッグモードがオプションで指定できます。 2つを設定し、実行すると以下のように必要事項を事前チェックし、その内容をすべて標準出力にプリントします。

まずは、ドライランモードで実行し問題ないことを確認します。

Tridentをインストールするネームスペースを作成します。

$ kubectl create ns trident

namespace/trident created

Tridentのインストーラーをドライランモードで実行します。

$ ./tridentctl install --dry-run -n trident -d

DEBU Initialized logging.                          logLevel=debug
DEBU Running outside a pod, creating CLI-based client.
DEBU Initialized Kubernetes CLI client.            cli=kubectl flavor=k8s namespace=default version=1.11.0
DEBU Validated installation environment.           installationNamespace=trident kubernetesVersion=
DEBU Parsed requested volume size.                 quantity=2Gi
DEBU Dumping RBAC fields.                          ucpBearerToken= ucpHost= useKubernetesRBAC=true
DEBU Namespace does not exist.                     namespace=trident
DEBU PVC does not exist.                           pvc=trident
DEBU PV does not exist.                            pv=trident
- snip
INFO Dry run completed, no problems found.
- snip

ドライランモードで実施すると問題ない旨(INFO Dry run completed, no problems found.) が表示されれば、インストールに必要な事前要件を満たしていることが確認できます。 バージョン、実行モードによってはログの途中に出力されることもあるためログを確認しましょう。

上記の状態まで確認できたら実際にインストールを実施します。

$ ./tridentctl install -n trident -d

DEBU Initialized logging.                          logLevel=debug
DEBU Running outside a pod, creating CLI-based client.
DEBU Initialized Kubernetes CLI client.            cli=kubectl flavor=k8s namespace=default version=1.11.0
DEBU Validated installation environment.           installationNamespace=trident kubernetesVersion=
DEBU Parsed requested volume size.                 quantity=2Gi
DEBU Dumping RBAC fields.                          ucpBearerToken= ucpHost= useKubernetesRBAC=true
DEBU Namespace does not exist.                     namespace=trident
DEBU PVC does not exist.                           pvc=trident
DEBU PV does not exist.                            pv=trident
- snip
INFO Trident installation succeeded.

「INFO Trident installation succeeded.」が出力されればインストール成功です。

また、問題が発生した場合には tridentctl を使用してtridentに関するログをまとめて確認することが出来ます。

$ ./tridentctl -n trident logs

time="2018-02-15T03:32:35Z" level=error msg="API invocation failed. Post https://10.0.1.146/servlets/netapp.servlets.admin.XMLrequest_filer: dial tcp 10.0.1.146:443: getsockopt: connection timed out"
time="2018-02-15T03:32:35Z" level=error msg="Problem initializing storage driver: 'ontap-nas' error: Error initializing ontap-nas driver. Could not determine Data ONTAP API version. Could not read ONTAPI version. Post https://10.0.1.146/servlets/netapp.servlets.admin.XMLrequest_filer: dial tcp 10.0.1.146:443: getsockopt: connection timed out" backend= handler=AddBackend
time="2018-02-15T03:32:35Z" level=info msg="API server REST call." duration=2m10.64501326s method=POST route=AddBackend uri=/trident/v1/backend
Tridentのバージョン確認

インストールが完了したらtridentのバージョンを確認します。

$ ./tridentctl version -n trident

+----------------+----------------+
| SERVER VERSION | CLIENT VERSION |
+----------------+----------------+
| 19.07.0        | 19.07.0        |
+----------------+----------------+

バージョンが表示されていればインストール成功です。

ヒント

tridentctl は tridentの podと通信をして制御を行います。 このため、各コマンドは tridentの podが存在するネームスペースを 指定する必要があります。

Tridentへのバックエンド登録

Tridentが、その背後で制御するストレージ(バックエンドストレージ)を登録します。

バックエンドストレージを設定するためにjsonファイルを用意します。 サンプルファイルがsample-inputディレクトリにあり、ここではONTAPのNASを設定しますので backend-ontap-nas.jsonをコピーして使います。

backend.jsonの設定パラメータ (NFS ONTAPバックエンド)
パラメータ名 説明 設定内容
managementLIF ONTAPのクラスタ管理LIFまたはSVM管理LIFを設定 192.168.XX.200
dataLIF データ通信LIF 192.168.XX.200
svm tridentから使用するSVM svmXX
username/password クラスタ管理者またはSVM管理者のクレデンシャル SVM管理者を設定: vsadmin/netapp123

編集後は以下の通りとなります。 疎通が取れないIPを設定するとバックエンド登録に失敗します。

$ cat setup/backend.json

{
    "version": 1,
    "storageDriverName": "ontap-nas",
    "backendName": "userXXBackendName",
    "managementLIF": "192.168.XX.200",
    "dataLIF": "192.168.XX.200",
    "svm": "svmXX",
    "username": "vsadmin",
    "password": "netapp123"
}

「XX」はラボ環境にあわせて設定してください。

編集したjsonファイルと``tridentctl create backend``を使ってバックエンドを登録します。

$ ./tridentctl -n trident create backend -f setup/backend.json

+-------------------+----------------+--------+---------+
|       NAME        | STORAGE DRIVER | ONLINE | VOLUMES |
+-------------------+----------------+--------+---------+
| userXXBackendName | ontap-nas      | true   |       0 |
+-------------------+----------------+--------+---------+
問題発生時に実施: Tridentをアンインストールする

トラブルシューティング時にTridentをアンインストールする必要が出てくるケースがあります。 その際には tridentctl ユーティリティのアンインストール用のサブコマンドを使用してアンインストールします。

インストール実行時に失敗したときなど、クリーンに再インストールしたい場合に使います。

$ ./tridentctl uninstall -n trident

ヒント

続けて trident namespaceも削除したい場合は、trident namespace 内に残るオブジェクトが完全になくなった事を確認する事をおすすめします。

Related Issue

Tridentインストールのバージョン間の差異

インストール時の動作が19.0xで何度か変化しているので整理しておきます。

Version インストール時backend.json 初回バックエンド登録
〜19.01 必要 されない
19.04 必要 される
19.07〜 不要 されない
インストール時のbackend.json

19.01/04では Tridentが構成情報を保存する Persistent Volumeを Provisioningするために setup/backend.jsonファイルを作成してから tridentctl install をする必要があります。

19.01では Trident Install後に改めて バックエンド登録をする必要がありました。 19.04では Trident Install時点で backend.jsonで指定したストレージがバックエンド登録されます。

19.07では インストール時に setup/backend.jsonが不要になりました。これはCustom Resource Definition を利用してTridentの構成情報を保存するように変更になった事によります。 ただし、これにより 19.07では インストール完了後にバックエンドの自動登録がされないため注意してください。 また、tridentctl installを実行するときのカレントパスに setup ディレクトリが必要なため注意してください。

過去のバージョンのインストール手順は下記を参考にしてください。

StorageClassの定義

StorageClassを定義して、ストレージのサービスカタログを作りましょう。

Trident v19.07 ではStorageClassを作成するときに以下の属性を設定できます。 これらの属性のパラメータを組み合わせてストレージサービスをデザインします。

StorageClass の parameters に設定可能な属性
設定可能な属性
性能に関する属性 メデイアタイプ(hdd, hybrid, ssd)、プロビジョニングのタイプ(シン、シック)、IOPS
データ保護・管理に関する属性 スナップショット有無、クローニング有効化、暗号化の有効化
バックエンドのストレージプラットフォーム属性 ontap-nas, ontap-nas-economy, ontap-nas-flexgroup, ontap-san, solidfire-san, eseries-iscsi

全てのパラメータ設定については以下のURLに記載があります。

NFSバックエンドのONTAPでのStorageClass

ストレージ構成は以下の通りです。 今回、意識する必要があるところは異なるメディアタイプ(HDDとSSD)のアグリゲートを保有しているところです。

  • 各SVMにHDD, SSDのアグリゲートを割り当て済み

    • aggr1_01:SSDのアグリゲート
    • aggr2_01:HDDのアグリゲート

以下のようなイメージでStoageClassを作成しましょう。

  • DB 用の高速領域: SSD を使ったストレージサービス
  • Web コンテンツ用のリポジトリ: HDDを使ったストレージサービス

以下は上記の「DB 用の高速領域」のStorageClass作成方法のサンプルです。

高速ストレージ用のマニフェストファイル例 StorageClassFastest.yml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ontap-gold
provisioner: netapp.io/trident
reclaimPolicy: Retain
parameters:
  backendType: "ontap-nas"
  media: "ssd"
  provisioningType: "thin"
  snapshots: "true"

ストレージクラスを作成します。

$ kubectl create -f StorageClassFastest.yml

storageclass "ontap-gold" created

$ kubectl get sc

NAME         PROVISIONER         AGE
ontap-gold   netapp.io/trident   10s

注釈

デフォルトのStorageClassの設定

StorageClassは記載がないときに使用するStorageClassを指定できます。

kubectl patch storageclass ストレージクラス名 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
Persistent Volume Claimの作成

アプリケーションで必要とされる永続化領域の定義をします。 PVCを作成時に独自の機能を有効化することができます。

データの保管ポリシー、データ保護ポリシー、SnapShotの取得ポリシー、クローニングの有効化、暗号化の有効化などを設定できます。

一覧については以下のURLに記載があります。 metadata.annotation 配下に記述することで様々な機能を使用することが可能となります。

デプロイ用のマニフェストファイルにPVCを追加

Level1で作成したマニフェストファイルにPVCの項目を追加し、ダイナミックプロビジョニングを使用しデータを永続化出来るアプリケーションを定義します。

高速ストレージ用の定義ファイルの例 PVCFastest.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sample-pv-claim
  labels:
    app: アプリケーション名
  annotations:
    trident.netapp.io/exportPolicy: "default"
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: ontap-gold

ここでやることは主に以下の2つを実施してください。 このセクションはどうやって実現するかを考えていただくためあえて答えは書いてありません。

  1. 上記PVCマニフェストファイルを作成し、PVCオブジェクトを作成
  2. Level1で作成したアプリケーションの永続化

ヒント

Level1で作成したマニフェストにはストレージの定義がありません。 どうやってストレージの定義をし永続化するかを考えてみましょう。 また、Level1のサンプルでは永続化する対象はデータベース(MySQL)になります。 MySQLのデータファイルはデフォルトでは /var/lib/mysql になります。

デプロイメント実施

上記のPVCの設定が終わったら再度アプリケーションをデプロイします。

その後、アプリケーションからデータを保存するようオペレーションを行います。 WordPressであれば記事を投稿することで簡単に確認ができます。

永続化できていることを確認するためアプリケーションの停止・起動を実施

永続化されていることを確認するため、一度アプリケーションを停止します。

Deploymentで必要となるポッドは起動するような設定になっているため、 簡単にアプリケーションの停止・起動を行う方法として Deployment 配下の Pod を削除する方法がとれます。

$ kubectl delete pod -l "ラベル名"

$ kubectl get deploy

実行例は以下の通りです。

$ kubectl delete pod -l app=wordpress # app=wordpress が付与されているポッドをすべて削除

pod "wordpress-5bc75fd7bd-kzc5l" deleted
pod "wordpress-mysql-565494758-jjdl4" deleted

$ kubectl get deploy # Podはデプロイメントで管理されているため、すぐ再起動される

NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
wordpress         1         1         1            0           31d
wordpress-mysql   1         1         1            0           31d

DeploymentによってPodの起動数は管理されるため新たにPodが起動します。 AVAILABLE の数が正常になるまで待ちましょう。

$ kubectl get deploy

NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
wordpress         1         1         1            1           31d
wordpress-mysql   1         1         1            1           31d
再デプロイメント後の確認

再起動したPodに対して永続化されたデータが使用されていることを確認します。 2つの視点から確認したいと思います。

  1. アプリケーションであれば再度ログインして保存したデータを確認します。
  2. バックエンドストレージに動的にボリュームが作成されていることを確認します。
$ ssh vsadmin@192.168.XX.200 vol show

Password:
Vserver   Volume       Aggregate    State      Type       Size  Available Used%
--------- ------------ ------------ ---------- ---- ---------- ---------- -----
tridentsvm root        aggr1        online     RW          1GB    972.2MB    5%
tridentsvm trident_trident aggr1    online     RW       1.86GB     1.77GB    5%
tridentsvm trident_trident_basic_f4048 aggr1 online RW     1GB    972.4MB    5%
3 entries were displayed.
Tridentの特徴的な機能: Volume Cloningのストレージオフロード

NetAppのストレージOSは FlexCloneや Cloneと呼ばれるストレージオフロード可能な、 高速データ複製機能=クローニングテクノロジーを利用できます。[1]

CSI Tridentでは PersistentVolumeClaimの dataSourceにコピー元となる PVC/Snapshotを指定する事で 巨大なボリュームでも容量消費せずに超高速にデータをコピーする クローニングテクノロジーが使用されます。

注釈

Volume Data Source Feature Gate Kubernetesで CSI Tridentを使って dataSourceを指定してCloningをするには featuregateを有効にする必要があります。

Feature Gates 機能 Kubernetes Support Feature Gate指定 Trident Support
VolumePVCDataSource PersistentVolumeClaimを指定してClone
Kubernetes 1.15 αサポート
Kubernetes 1.16 βサポート
必要
不要
19.10〜
19.10〜
VolumeSnapshotDataSource VolumeSnapshotを指定してClone Kubernetes 1.12 αサポート 必要 19.07〜

kindにデータソースの種類(VolumeSnapshot/PersistentVolumeClaim)とnameにデータソースの名前を指定します。

CSI Trident- dataSourceを持つPVCの例 csidatasource.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: clone-of-pvc-1
  namespace: myns
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  dataSource: (このフィールドにオブジェクトの種類と名前を記述)
    kind: PersistentVolumeClaim
    name: pvc-1

従来の Tridentでも、PVCアノテーションである、trident.netapp.io/cloneFromPVC を介してクローニングテクノロジーを利用できます。

引数にPVC名を指定します。

Trident- アノテーションを持ったPVCの例 pvccloning.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: basicclone
  annotations:
    trident.netapp.io/cloneFromPVC: database (<-ここにクローンしたい既存のPVC名(ボリューム名)を記述)
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: ontap-gold

ここではサンプルでPVC Cloning を活用したOracle Databaseを複数デプロイするデモ動画をご覧ください。

[1]必要な機能が利用できるモデル/ライセンスである事をご確認ください。
クローニング技術によって実現可能なこと

クローニング技術はシンプルですが非常に多く用途で使用することができます。 例としてあげられるのが以下の用途です。

  • プレビルド環境の高速展開
  • 本番環境に影響せずに大規模な並列テスト
  • 運用時のデータリストアの高速化、瞬時に論理障害を戻す
Tridentのアップグレード方法

TridentはKubernetesのアップデートと同じサイクルでリリースされています。 Kubernetesのバージョンがあがってから約1ヶ月以内くらいにTridentがリリースされます。

kubernetesのバージョンにサポートの有無があります。

バージョンに対応したTridentを確認するには以下のページを参照ください。

https://netapp-trident.readthedocs.io/en/stable-v19.07/support/requirements.html

すでにインストールされているTridentをアップグレードするには以下のようにアンインストール、インストールをくりかえすことで実現できます。

$ ./tridentctl uninstall -n <namespace>
$ ./tridentctl install   -n <namespace>

アンインストールコマンドのデフォルトの挙動はTridentでデプロイされたPVC,PVは削除しません。 Tridentの状態をそのままにしているため、アンインストール後にインストールをすることでアップグレードとして機能させることができます。

Tridentアップグレード時の注意

アップグレード時に Kubernetesのバージョンによっては考慮が必要な場合があります。

また、19.07では etcdから CRDへと Tridentの構成情報の記録場所が変更になったため、アップグレード時には、そのコピーが行われます。

アップグレード中の動作等、詳細は下記のアップグレードについてのオフィシャルドキュメントを参照してください。

https://netapp-trident.readthedocs.io/en/stable-v19.07/kubernetes/upgrading.html

まとめ

アプリケーションに対して動的に永続化領域をプロビジョニングしデータの永続化を実現しました。

今回はStorageClassの作成からアプリケーションにPersistentVolumeを割り当てるところまでを一連の流れで実現しました。

運用を考えた場合、それぞれのコンポーネントで担当が異なるため以下のような分担になるかと思います。

  • StorageClassの作成: インフラ・kubernetesクラスタの管理者
  • PersistentVolumeClaimの作成: アプリケーション開発者

今後障害時の動作が気になると思いますが、 Level 4: 運用編 での検討事項とします。

ここまでで Level2 は終了です。

Level 3: CI/CDパイプラインを構築

目的・ゴール: コンテナ化したアプリケーションのCICDを実現する

アプリケーションをコンテナ化したら、常にリリース可能な状態、自動でデプロイメントを出来る仕組みをつくるのが迅速な開発をするために必要になります。

そのためのCI/CDパイプラインを作成するのがこのレベルの目標です。

以下の図はこのレベルでCICDパイプラインを実現するためのツールを表したものになります。 実現するためには様々なツールが存在します。以下のツールはあくまで1例と捉えてください。

_images/cicd_pipeline.png

登場しているツールの以下のように分類でき、それぞれ代表的なものをキーワードとして上げます。

  • SCM: Git, GitHub, GitLab
  • CICD: Jenkins, JenkinsX, Spinnaker, GitLab Runner
  • アーティファクト管理: JFrog
  • Image Registry: Harbor, DockerRegistry, GitLab
  • Package管理: Helm

本ラボでは Level1, Level2 で行ったオペレーションをベースにCI/CDパイプラインを構築します。

Gitにソースがコミットされたら自動でテスト・ビルドを実現するためのツール(Jenkins)をkubernetes上へデプロイ、及び外部公開をします。 そして、Jenkinsがデプロイできたら実際にアプリケーションの変更を行い自動でデプロイするところまでを目指します。

流れ
  1. Jenkins をインストールする
  2. Jenkins 内部でジョブを定義する。
  3. あるアクションをトリガーにビルド、テストを自動実行する。
  4. 自動でk8sクラスタにデプロイメントできるようにする。
CI/CDパイプラインの定義

このラボでのCI/CDパイプラインの定義は以下を想定しています。

  • アプリケーションビルド
  • コンテナイメージのビルド
  • レジストリへコンテナイメージのpush
  • テスト実行
  • k8sへアプリケーションデプロイ

GitはGitLabを共有で準備していますが、使いなれているサービス(GitHub等)があればそちらを使って頂いても構いません。 まずは、Jenkinsをkubernetes上にデプロイしてみましょう。

Git自体も併せてデプロイしてみたいということであればGitLabをデプロイすることをおすすめします。 GitLabを使えばコンテナのCI/CDパイプライン、構成管理、イメージレジストリを兼ねて使用することができます。

Jenkinsのデプロイ方法について

CI/CDパイプラインを実現するためのツールとしてJenkinsが非常に有名であることは周知の事実です。 このラボではJenkinsを使用しCI/CDを実現します。

まずは、各自Jenkinsをデプロイします。

方法としては3つ存在します。

  1. Helm Chartでデプロイする方法 (手軽にインストールしたい人向け)
  2. Level1,2と同じようにyamlファイルを作成し、デプロイする方法(仕組みをより深く知りたい人向け)
  3. Kubernetes用にCI/CDを提供するJenkins Xをデプロイする方法(新しい物を使いたい人向け)

今回は最初のHelmでデプロイするバージョンを記載しました。 好みのもの、挑戦したい内容に沿って選択してください。

オリジナルでyamlファイルを作成する場合は以下のサイトが参考になります。

Helmを使ってJenkinsをデプロイ
Helmの初期化

Helmを使用する事前の設定をします。 Helmの初期化、RBACの設定を実施します。

$ helm init
$ kubectl create clusterrolebinding add-on-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:default
Helmの基本

基本的なHelmの使い方は以下の通りです。

$ helm install stable/helm-chart名
Helmチャートのインストール・Jenkinsのカスタマイズ

今回はJenkinsを導入するにあたり環境に併せてカスタマイズを行います。 Helmは以下のURLに様々なものが公開されています。パラメータを与えることである程度カスタマイズし使用することができます。 Helm chartと同等のディレクトリにvalues.yamlというファイルが存在し、これを環境に併せて変更することでカスタマイズしデプロイできます。

今回のJenkinsのデプロイでは2つの公開方法が選択できます。

1つ目が、今回の環境では ServicetypeLoadBalancer としてしてデプロイすると external-ipが付与される環境となっています。(MetalLBをデプロイ済みです。)

2つ目が Ingress を使った公開です。IngressをJenkinsのHelmチャートを使ってデプロイするためには「Master.Ingress.Annotations」、「Master.ServiceType」を変更しデプロイします。

簡易的にデプロイをためしてみたい方は1つ目の LoadBalancer を使ったやり方を実施、新しい概念であるIngressを使った方法を実施したい方は2つ目を選択しましょう。

どちらの方法の場合も、以下のvalues.yamlをカスタマイズしてデプロイします。 このレベルではJenkinsをデプロイするのが目的ではなくCI/CDパイプラインを作成するのが目的であるため、デプロイ用のyamlファイルを準備しました。

また、このvalues.yamlでは永続化ストレージが定義されていないため、Level2で作成したStorageClassを使用し動的にプロビジョニングをするように変更しましょう。

StorageClassには環境に作成したStorageClassを設定します。このサンプルでは "ontap-gold"を設定してあります。

また、Kubernetes上でCI/CDパイプラインを作成するため Kubernetes-plugin もyamlファイルに追記済みです。

Helm設定用のvalues.yaml
# Default values for jenkins.
# This is a YAML-formatted file.
# Declare name/value pairs to be passed into your templates.
# name: value

## Overrides for generated resource names
# See templates/_helpers.tpl
# nameOverride:
# fullnameOverride:

Master:
  Name: jenkins-master
  Image: "jenkins/jenkins"
  ImageTag: "lts"
  ImagePullPolicy: "Always"
  # ImagePullSecret: jenkins
  Component: "jenkins-master"
  UseSecurity: true
  AdminUser: admin
  # AdminPassword: <defaults to random>
  resources:
    requests:
      cpu: "50m"
      memory: "256Mi"
    limits:
      cpu: "2000m"
      memory: "2048Mi"
  # Environment variables that get added to the init container (useful for e.g. http_proxy)
  # InitContainerEnv:
  #   - name: http_proxy
  #     value: "http://192.168.64.1:3128"
  # ContainerEnv:
  #   - name: http_proxy
  #     value: "http://192.168.64.1:3128"
  # Set min/max heap here if needed with:
  # JavaOpts: "-Xms512m -Xmx512m"
  # JenkinsOpts: ""
  # JenkinsUriPrefix: "/jenkins"
  # Enable pod security context (must be `true` if RunAsUser or FsGroup are set)
  UsePodSecurityContext: true
  # Set RunAsUser to 1000 to let Jenkins run as non-root user 'jenkins' which exists in 'jenkins/jenkins' docker image.
  # When setting RunAsUser to a different value than 0 also set FsGroup to the same value:
  # RunAsUser: <defaults to 0>
  # FsGroup: <will be omitted in deployment if RunAsUser is 0>
  ServicePort: 8080
  # For minikube, set this to NodePort, elsewhere use LoadBalancer
  # Use ClusterIP if your setup includes ingress controller
  ServiceType: LoadBalancer
  # Master Service annotations
  ServiceAnnotations: {}
  #   service.beta.kubernetes.io/aws-load-balancer-backend-protocol: https
  # Used to create Ingress record (should used with ServiceType: ClusterIP)
  # HostName: jenkins.cluster.local
  # NodePort: <to set explicitly, choose port between 30000-32767
  # Enable Kubernetes Liveness and Readiness Probes
  # ~ 2 minutes to allow Jenkins to restart when upgrading plugins. Set ReadinessTimeout to be shorter than LivenessTimeout.
  HealthProbes: true
  HealthProbesLivenessTimeout: 90
  HealthProbesReadinessTimeout: 60
  HealthProbeLivenessFailureThreshold: 12
  SlaveListenerPort: 50000
  DisabledAgentProtocols:
  - JNLP-connect
  - JNLP2-connect
  CSRF:
    DefaultCrumbIssuer:
      Enabled: true
      ProxyCompatability: true
  CLI: false
  # Kubernetes service type for the JNLP slave service
  # SETTING THIS TO "LoadBalancer" IS A HUGE SECURITY RISK: https://github.com/kubernetes/charts/issues/1341
  SlaveListenerServiceType: ClusterIP
  SlaveListenerServiceAnnotations: {}
  LoadBalancerSourceRanges:
  - 0.0.0.0/0
  # Optionally assign a known public LB IP
  # LoadBalancerIP: 1.2.3.4
  # Optionally configure a JMX port
  # requires additional JavaOpts, ie
  # JavaOpts: >
  #   -Dcom.sun.management.jmxremote.port=4000
  #   -Dcom.sun.management.jmxremote.authenticate=false
  #   -Dcom.sun.management.jmxremote.ssl=false
  # JMXPort: 4000
  # List of plugins to be install during Jenkins master start
  InstallPlugins:
  - kubernetes:1.12.3
  - workflow-job:2.24
  - workflow-aggregator:2.5
  - credentials-binding:1.16
  - git:3.9.1
  - blueocean:1.8.2
  - ghprb:1.40.0

  # Used to approve a list of groovy functions in pipelines used the script-security plugin. Can be viewed under /scriptApproval
  # ScriptApproval:
  #   - "method groovy.json.JsonSlurperClassic parseText java.lang.String"
  #   - "new groovy.json.JsonSlurperClassic"
  # List of groovy init scripts to be executed during Jenkins master start
  InitScripts:
  #  - |
  #    print 'adding global pipeline libraries, register properties, bootstrap jobs...'
  # Kubernetes secret that contains a 'credentials.xml' for Jenkins
  # CredentialsXmlSecret: jenkins-credentials
  # Kubernetes secret that contains files to be put in the Jenkins 'secrets' directory,
  # useful to manage encryption keys used for credentials.xml for instance (such as
  # master.key and hudson.util.Secret)
  # SecretsFilesSecret: jenkins-secrets
  # Jenkins XML job configs to provision
  # Jobs: |-
  #   test: |-
  #     <<xml here>>
  CustomConfigMap: false
  # Node labels and tolerations for pod assignment
  # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
  # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature
  NodeSelector: {}
  Tolerations: {}
  PodAnnotations: {}

  Ingress:
    ApiVersion: extensions/v1beta1
    Annotations:
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"

    TLS:
    # - secretName: jenkins.cluster.local
    #   hosts:
    #     - jenkins.cluster.local

Agent:
  Enabled: true
  Image: jenkins/jnlp-slave
  ImageTag: 3.10-1
  # ImagePullSecret: jenkins
  Component: "jenkins-slave"
  Privileged: false
  resources:
    requests:
      cpu: "200m"
      memory: "256Mi"
    limits:
      cpu: "200m"
      memory: "256Mi"
  # You may want to change this to true while testing a new image
  AlwaysPullImage: false
  # Controls how slave pods are retained after the Jenkins build completes
  # Possible values: Always, Never, OnFailure
  PodRetention: Never
  # You can define the volumes that you want to mount for this container
  # Allowed types are: ConfigMap, EmptyDir, HostPath, Nfs, Pod, Secret
  # Configure the attributes as they appear in the corresponding Java class for that type
  # https://github.com/jenkinsci/kubernetes-plugin/tree/master/src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes
  volumes:
  # - type: Secret
  #   secretName: mysecret
  #   mountPath: /var/myapp/mysecret
  NodeSelector: {}
  # Key Value selectors. Ex:
  # jenkins-agent: v1

Persistence:
  Enabled: true
  ## A manually managed Persistent Volume and Claim
  ## Requires Persistence.Enabled: true
  ## If defined, PVC must be created manually before volume will be bound
  # ExistingClaim:

  ## jenkins data Persistent Volume Storage Class
  ## If defined, storageClassName: <storageClass>ls
  ## If set to "-", storageClassName: "", which disables dynamic provisioning
  ## If undefined (the default) or set to null, no storageClassName spec is
  ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
  ##   GKE, AWS & OpenStack)
  ##
  StorageClass: "ontap-gold"

  Annotations: {}
  AccessMode: ReadWriteOnce
  Size: 8Gi
  volumes:
  #  - name: nothing
  #    emptyDir: {}
  mounts:
  #  - mountPath: /var/nothing
  #    name: nothing
  #    readOnly: true

NetworkPolicy:
  # Enable creation of NetworkPolicy resources.
  Enabled: false
  # For Kubernetes v1.4, v1.5 and v1.6, use 'extensions/v1beta1'
  # For Kubernetes v1.7, use 'networking.k8s.io/v1'
  ApiVersion: extensions/v1beta1

## Install Default RBAC roles and bindings
rbac:
  install: false
  serviceAccountName: default
  # RBAC api version (currently either v1, v1beta1, or v1alpha1)
  apiVersion: v1
  # Role reference
  roleRef: cluster-admin
  # Role kind (RoleBinding or ClusterRoleBinding)
  roleBindingKind: ClusterRoleBinding

実行イメージとしては以下の通りです。

$ helm --namespace jenkins --name jenkins -f ./jenkins-values.yaml install stable/jenkins --debug
[debug] Created tunnel using local port: '44511'

[debug] SERVER: "127.0.0.1:44511"

[debug] Original chart version: ""
[debug] Fetched stable/jenkins to /home/localadmin/.helm/cache/archive/jenkins-0.16.20.tgz

[debug] CHART PATH: /home/localadmin/.helm/cache/archive/jenkins-0.16.20.tgz

NAME:   jenkins
REVISION: 1
RELEASED: Mon Aug 27 23:54:09 2018
CHART: jenkins-0.16.20
USER-SUPPLIED VALUES:
Agent:
  AlwaysPullImage: false
  Component: jenkins-slave
  Enabled: true
  Image: jenkins/jnlp-slave
  ImageTag: 3.10-1
  NodeSelector: {}
  PodRetention: Never
  Privileged: false
  resources:
    limits:
      cpu: 200m
      memory: 256Mi
    requests:
      cpu: 200m
      memory: 256Mi
  volumes: null
Master:
  AdminUser: admin
  CLI: false
  CSRF:
    DefaultCrumbIssuer:
      Enabled: true
      ProxyCompatability: true
  Component: jenkins-master
  CustomConfigMap: false
  DisabledAgentProtocols:
  - JNLP-connect
  - JNLP2-connect
  HealthProbeLivenessFailureThreshold: 12
  HealthProbes: true
  HealthProbesLivenessTimeout: 90
  HealthProbesReadinessTimeout: 60
  Image: jenkins/jenkins
  ImagePullPolicy: Always
  ImageTag: lts
  Ingress:
    Annotations: null
    ApiVersion: extensions/v1beta1
    TLS: null
  InitScripts: null
  InstallPlugins:
  - kubernetes:1.12.3
  - workflow-job:2.24
  - workflow-aggregator:2.5
  - credentials-binding:1.16
  - git:3.9.1
  - blueocean:1.4.1
  - ghprb:1.40.0
  LoadBalancerSourceRanges:
  - 0.0.0.0/0
  Name: jenkins-master
  NodeSelector: {}
  PodAnnotations: {}
  ServiceAnnotations: {}
  ServicePort: 8080
  ServiceType: LoadBalancer
  SlaveListenerPort: 50000
  SlaveListenerServiceAnnotations: {}
  SlaveListenerServiceType: ClusterIP
  Tolerations: {}
  UsePodSecurityContext: true
  UseSecurity: true
  resources:
    limits:
      cpu: 2000m
      memory: 2048Mi
    requests:
      cpu: 50m
      memory: 256Mi
NetworkPolicy:
  ApiVersion: extensions/v1beta1
  Enabled: false
Persistence:
  AccessMode: ReadWriteOnce
  Annotations: {}
  Enabled: true
  Size: 8Gi
  StorageClass: ontap-gold
  mounts: null
  volumes: null
rbac:
  apiVersion: v1
  install: false
  roleBindingKind: ClusterRoleBinding
  roleRef: cluster-admin
  serviceAccountName: default

COMPUTED VALUES:
Agent:
  AlwaysPullImage: false
  Component: jenkins-slave
  Enabled: true
  Image: jenkins/jnlp-slave
  ImageTag: 3.10-1
  NodeSelector: {}
  PodRetention: Never
  Privileged: false
  resources:
    limits:
      cpu: 200m
      memory: 256Mi
    requests:
      cpu: 200m
      memory: 256Mi
  volumes: null
Master:
  AdminUser: admin
  CLI: false
  CSRF:
    DefaultCrumbIssuer:
      Enabled: true
      ProxyCompatability: true
  Component: jenkins-master
  CustomConfigMap: false
  DisabledAgentProtocols:
  - JNLP-connect
  - JNLP2-connect
  HealthProbeLivenessFailureThreshold: 12
  HealthProbes: true
  HealthProbesLivenessTimeout: 90
  HealthProbesReadinessTimeout: 60
  Image: jenkins/jenkins
  ImagePullPolicy: Always
  ImageTag: lts
  Ingress:
    Annotations: null
    ApiVersion: extensions/v1beta1
    TLS: null
  InitScripts: null
  InstallPlugins:
  - kubernetes:1.12.3
  - workflow-job:2.24
  - workflow-aggregator:2.5
  - credentials-binding:1.16
  - git:3.9.1
  - blueocean:1.4.1
  - ghprb:1.40.0
  LoadBalancerSourceRanges:
  - 0.0.0.0/0
  Name: jenkins-master
  NodeSelector: {}
  PodAnnotations: {}
  ServiceAnnotations: {}
  ServicePort: 8080
  ServiceType: LoadBalancer
  SlaveListenerPort: 50000
  SlaveListenerServiceAnnotations: {}
  SlaveListenerServiceType: ClusterIP
  Tolerations: {}
  UsePodSecurityContext: true
  UseSecurity: true
  resources:
    limits:
      cpu: 2000m
      memory: 2048Mi
    requests:
      cpu: 50m
      memory: 256Mi
NetworkPolicy:
  ApiVersion: extensions/v1beta1
  Enabled: false
Persistence:
  AccessMode: ReadWriteOnce
  Annotations: {}
  Enabled: true
  Size: 8Gi
  StorageClass: ontap-gold
  mounts: null
  volumes: null
rbac:
  apiVersion: v1
  install: false
  roleBindingKind: ClusterRoleBinding
  roleRef: cluster-admin
  serviceAccountName: default

HOOKS:
---
# jenkins-ui-test-1g5nb
apiVersion: v1
kind: Pod
metadata:
  name: "jenkins-ui-test-1g5nb"
  annotations:
    "helm.sh/hook": test-success
spec:
  initContainers:
    - name: "test-framework"
      image: "dduportal/bats:0.4.0"
      command:
      - "bash"
      - "-c"
      - |
        set -ex
        # copy bats to tools dir
        cp -R /usr/local/libexec/ /tools/bats/
      volumeMounts:
      - mountPath: /tools
        name: tools
  containers:
    - name: jenkins-ui-test
      image: jenkins/jenkins:lts
      command: ["/tools/bats/bats", "-t", "/tests/run.sh"]
      volumeMounts:
      - mountPath: /tests
        name: tests
        readOnly: true
      - mountPath: /tools
        name: tools
  volumes:
  - name: tests
    configMap:
      name: jenkins-tests
  - name: tools
    emptyDir: {}
  restartPolicy: Never
MANIFEST:

---
# Source: jenkins/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: jenkins
  labels:
    app: jenkins
    chart: "jenkins-0.16.20"
    release: "jenkins"
    heritage: "Tiller"
type: Opaque
data:

  jenkins-admin-password: "N3EwZWtydDAyQg=="

  jenkins-admin-user: "YWRtaW4="
---
# Source: jenkins/templates/config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: jenkins
data:
  config.xml: |-
    <?xml version='1.0' encoding='UTF-8'?>
    <hudson>
      <disabledAdministrativeMonitors/>
      <version>lts</version>
      <numExecutors>0</numExecutors>
      <mode>NORMAL</mode>
      <useSecurity>true</useSecurity>
      <authorizationStrategy class="hudson.security.FullControlOnceLoggedInAuthorizationStrategy">
        <denyAnonymousReadAccess>true</denyAnonymousReadAccess>
      </authorizationStrategy>
      <securityRealm class="hudson.security.LegacySecurityRealm"/>
      <disableRememberMe>false</disableRememberMe>
      <projectNamingStrategy class="jenkins.model.ProjectNamingStrategy$DefaultProjectNamingStrategy"/>
      <workspaceDir>${JENKINS_HOME}/workspace/${ITEM_FULLNAME}</workspaceDir>
      <buildsDir>${ITEM_ROOTDIR}/builds</buildsDir>
      <markupFormatter class="hudson.markup.EscapedMarkupFormatter"/>
      <jdks/>
      <viewsTabBar class="hudson.views.DefaultViewsTabBar"/>
      <myViewsTabBar class="hudson.views.DefaultMyViewsTabBar"/>
      <clouds>
        <org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud plugin="kubernetes@1.12.3">
          <name>kubernetes</name>
          <templates>
            <org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
              <inheritFrom></inheritFrom>
              <name>default</name>
              <instanceCap>2147483647</instanceCap>
              <idleMinutes>0</idleMinutes>
              <label>jenkins-jenkins-slave</label>
              <nodeSelector></nodeSelector>
                <nodeUsageMode>NORMAL</nodeUsageMode>
              <volumes>
              </volumes>
              <containers>
                <org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
                  <name>jnlp</name>
                  <image>jenkins/jnlp-slave:3.10-1</image>
                  <privileged>false</privileged>
                  <alwaysPullImage>false</alwaysPullImage>
                  <workingDir>/home/jenkins</workingDir>
                  <command></command>
                  <args>${computer.jnlpmac} ${computer.name}</args>
                  <ttyEnabled>false</ttyEnabled>
                  # Resources configuration is a little hacky. This was to prevent breaking
                  # changes, and should be cleanned up in the future once everybody had
                  # enough time to migrate.
                  <resourceRequestCpu>200m</resourceRequestCpu>
                  <resourceRequestMemory>256Mi</resourceRequestMemory>
                  <resourceLimitCpu>200m</resourceLimitCpu>
                  <resourceLimitMemory>256Mi</resourceLimitMemory>
                  <envVars>
                    <org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
                      <key>JENKINS_URL</key>
                      <value>http://jenkins:8080</value>
                    </org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar>
                  </envVars>
                </org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate>
              </containers>
              <envVars/>
              <annotations/>
              <imagePullSecrets/>
              <nodeProperties/>
              <podRetention class="org.csanchez.jenkins.plugins.kubernetes.pod.retention.Default"/>
            </org.csanchez.jenkins.plugins.kubernetes.PodTemplate></templates>
          <serverUrl>https://kubernetes.default</serverUrl>
          <skipTlsVerify>false</skipTlsVerify>
          <namespace>jenkins</namespace>
          <jenkinsUrl>http://jenkins:8080</jenkinsUrl>
          <jenkinsTunnel>jenkins-agent:50000</jenkinsTunnel>
          <containerCap>10</containerCap>
          <retentionTimeout>5</retentionTimeout>
          <connectTimeout>0</connectTimeout>
          <readTimeout>0</readTimeout>
          <podRetention class="org.csanchez.jenkins.plugins.kubernetes.pod.retention.Never"/>
        </org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud>
      </clouds>
      <quietPeriod>5</quietPeriod>
      <scmCheckoutRetryCount>0</scmCheckoutRetryCount>
      <views>
        <hudson.model.AllView>
          <owner class="hudson" reference="../../.."/>
          <name>All</name>
          <filterExecutors>false</filterExecutors>
          <filterQueue>false</filterQueue>
          <properties class="hudson.model.View$PropertyList"/>
        </hudson.model.AllView>
      </views>
      <primaryView>All</primaryView>
      <slaveAgentPort>50000</slaveAgentPort>
      <disabledAgentProtocols>
        <string>JNLP-connect</string>
        <string>JNLP2-connect</string>
      </disabledAgentProtocols>
      <label></label>
      <crumbIssuer class="hudson.security.csrf.DefaultCrumbIssuer">
        <excludeClientIPFromCrumb>true</excludeClientIPFromCrumb>
      </crumbIssuer>
      <nodeProperties/>
      <globalNodeProperties/>
      <noUsageStatistics>true</noUsageStatistics>
    </hudson>
  jenkins.model.JenkinsLocationConfiguration.xml: |-
    <?xml version='1.1' encoding='UTF-8'?>
    <jenkins.model.JenkinsLocationConfiguration>
      <adminAddress></adminAddress>
      <jenkinsUrl>http://jenkins:8080</jenkinsUrl>
    </jenkins.model.JenkinsLocationConfiguration>
  jenkins.CLI.xml: |-
    <?xml version='1.1' encoding='UTF-8'?>
    <jenkins.CLI>
      <enabled>false</enabled>
    </jenkins.CLI>
  apply_config.sh: |-
    mkdir -p /usr/share/jenkins/ref/secrets/;
    echo "false" > /usr/share/jenkins/ref/secrets/slave-to-master-security-kill-switch;
    cp -n /var/jenkins_config/config.xml /var/jenkins_home;
    cp -n /var/jenkins_config/jenkins.CLI.xml /var/jenkins_home;
    cp -n /var/jenkins_config/jenkins.model.JenkinsLocationConfiguration.xml /var/jenkins_home;
    # Install missing plugins
    cp /var/jenkins_config/plugins.txt /var/jenkins_home;
    rm -rf /usr/share/jenkins/ref/plugins/*.lock
    /usr/local/bin/install-plugins.sh `echo $(cat /var/jenkins_home/plugins.txt)`;
    # Copy plugins to shared volume
    cp -n /usr/share/jenkins/ref/plugins/* /var/jenkins_plugins;
  plugins.txt: |-
    kubernetes:1.12.3
    workflow-job:2.24
    workflow-aggregator:2.5
    credentials-binding:1.16
    git:3.9.1
    blueocean:1.4.1
    ghprb:1.40.0
---
# Source: jenkins/templates/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: jenkins-tests
data:
  run.sh: |-
    @test "Testing Jenkins UI is accessible" {
      curl --retry 48 --retry-delay 10 jenkins:8080/login
    }
---
# Source: jenkins/templates/home-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: jenkins
  labels:
    app: jenkins
    chart: "jenkins-0.16.20"
    release: "jenkins"
    heritage: "Tiller"
spec:
  accessModes:
    - "ReadWriteOnce"
  resources:
    requests:
      storage: "8Gi"
  storageClassName: "ontap-gold"
---
# Source: jenkins/templates/jenkins-agent-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: jenkins-agent
  labels:
    app: jenkins
    chart: "jenkins-0.16.20"
    component: "jenkins-jenkins-master"
spec:
  ports:
    - port: 50000
      targetPort: 50000

      name: slavelistener
  selector:
    component: "jenkins-jenkins-master"
  type: ClusterIP
---
# Source: jenkins/templates/jenkins-master-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: jenkins
  labels:
    app: jenkins
    heritage: "Tiller"
    release: "jenkins"
    chart: "jenkins-0.16.20"
    component: "jenkins-jenkins-master"
spec:
  ports:
    - port: 8080
      name: http
      targetPort: 8080

  selector:
    component: "jenkins-jenkins-master"
  type: LoadBalancer

  loadBalancerSourceRanges:
    - 0.0.0.0/0
---
# Source: jenkins/templates/jenkins-master-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: jenkins
  labels:
    heritage: "Tiller"
    release: "jenkins"
    chart: "jenkins-0.16.20"
    component: "jenkins-jenkins-master"
spec:
  replicas: 1
  strategy:
    type: RollingUpdate
  selector:
    matchLabels:
      component: "jenkins-jenkins-master"
  template:
    metadata:
      labels:
        app: jenkins
        heritage: "Tiller"
        release: "jenkins"
        chart: "jenkins-0.16.20"
        component: "jenkins-jenkins-master"
      annotations:
        checksum/config: f1949fdff0e0d3db7c6180357f63c007db61b13e5c107e5980a5eac982863c21
    spec:
      securityContext:
        runAsUser: 0
      serviceAccountName: "default"
      initContainers:
        - name: "copy-default-config"
          image: "jenkins/jenkins:lts"
          imagePullPolicy: "Always"
          command: [ "sh", "/var/jenkins_config/apply_config.sh" ]
          resources:
            limits:
              cpu: 2000m
              memory: 2048Mi
            requests:
              cpu: 50m
              memory: 256Mi

          volumeMounts:
            -
              mountPath: /var/jenkins_home
              name: jenkins-home
            -
              mountPath: /var/jenkins_config
              name: jenkins-config
            -
              mountPath: /var/jenkins_plugins
              name: plugin-dir
            -
              mountPath: /usr/share/jenkins/ref/secrets/
              name: secrets-dir
      containers:
        - name: jenkins
          image: "jenkins/jenkins:lts"
          imagePullPolicy: "Always"
          args: [ "--argumentsRealm.passwd.$(ADMIN_USER)=$(ADMIN_PASSWORD)",  "--argumentsRealm.roles.$(ADMIN_USER)=admin"]
          env:
            - name: JAVA_TOOL_OPTIONS
              value: ""
            - name: JENKINS_OPTS
              value: ""
            - name: ADMIN_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: jenkins
                  key: jenkins-admin-password
            - name: ADMIN_USER
              valueFrom:
                secretKeyRef:
                  name: jenkins
                  key: jenkins-admin-user
          ports:
            - containerPort: 8080
              name: http
            - containerPort: 50000
              name: slavelistener
          livenessProbe:
            httpGet:
              path: "/login"
              port: http
            initialDelaySeconds: 90
            timeoutSeconds: 5
            failureThreshold: 12
          readinessProbe:
            httpGet:
              path: "/login"
              port: http
            initialDelaySeconds: 60
          # Resources configuration is a little hacky. This was to prevent breaking
          # changes, and should be cleanned up in the future once everybody had
          # enough time to migrate.
          resources:

            limits:
              cpu: 2000m
              memory: 2048Mi
            requests:
              cpu: 50m
              memory: 256Mi


          volumeMounts:
            -
              mountPath: /var/jenkins_home
              name: jenkins-home
              readOnly: false
            -
              mountPath: /var/jenkins_config
              name: jenkins-config
              readOnly: true
            -
              mountPath: /usr/share/jenkins/ref/plugins/
              name: plugin-dir
              readOnly: false
            -
              mountPath: /usr/share/jenkins/ref/secrets/
              name: secrets-dir
              readOnly: false
      volumes:
      - name: jenkins-config
        configMap:
          name: jenkins
      - name: plugin-dir
        emptyDir: {}
      - name: secrets-dir
        emptyDir: {}
      - name: jenkins-home
        persistentVolumeClaim:
          claimName: jenkins
LAST DEPLOYED: Mon Aug 27 23:54:09 2018
NAMESPACE: jenkins
STATUS: DEPLOYED

RESOURCES:
==> v1/Secret
NAME     TYPE    DATA  AGE
jenkins  Opaque  2     0s

==> v1/ConfigMap
NAME           DATA  AGE
jenkins        5     0s
jenkins-tests  1     0s

==> v1/PersistentVolumeClaim
NAME     STATUS   VOLUME      CAPACITY  ACCESS MODES  STORAGECLASS  AGE
jenkins  Pending  ontap-gold  0s

==> v1/Service
NAME           TYPE          CLUSTER-IP     EXTERNAL-IP     PORT(S)         AGE
jenkins-agent  ClusterIP     10.109.172.86  <none>          50000/TCP       0s
jenkins        LoadBalancer  10.97.161.136  192.168.10.210  8080:30376/TCP  0s

==> v1beta1/Deployment
NAME     DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
jenkins  1        1        1           0          0s

==> v1/Pod(related)
NAME                     READY  STATUS   RESTARTS  AGE
jenkins-965668c95-7tzmc  0/1    Pending  0         0s


NOTES:
1. Get your 'admin' user password by running:
  printf $(kubectl get secret --namespace jenkins jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);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 --namespace jenkins -w jenkins'
  export SERVICE_IP=$(kubectl get svc --namespace jenkins jenkins --template "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}")
  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

「NOTES」欄に記載の通りadminパスワードを取得します。

$ printf $(kubectl get secret --namespace jenkins jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo

    sShJg2gig9

以上で、Jenkinsのデプロイが完了しました。

Helmが生成するマニフェストファイル

Helmを使いvalues.yamlを定義するとどのようなマニフェストファイルが生成されるかが予測しづらいこともあります。

その場合には --dry-run--debug を付与することでデプロイメントされるYAMLファイルが出力されます。

$ helm --namespace jenkins --name jenkins -f ./values.yaml install stable/jenkins --dry-run --debug
values.yamlのTry & Error: インストールが上手くいかない場合は?

values.yamlを試行錯誤しながら設定していくことになると思います。 一度デプロイメントしたHelmチャートは以下のコマンドで削除することができます。

$ helm del --purge チャート名
Helm以外でJenkinsをデプロイした場合

本セクションに記載してあることはオプションです。

必要に応じて実施してください。

外部にアプリケーションを公開する方法として Ingress があります。 Helmを使ってJenkinsをインストー時にvalues.yamlで設定を行うことでIngressが作成されます。 それ以外の手法を取った場合は、kubernetesクラスタ外のネットワークからアクセスできるようにIngressを作成しアクセスする方法があります。

Ingressの導入についてはLevel4 運用編の Ingressを導入 にまとめました。

Jenkinsの設定をする

Gitリポジトリに変更があったら自動でテストを実行するpipelineを定義します。 そのためにはまずJenkinsでGitリポジトリに操作があった場合の動作の定義とKubernetesとの接続の設定をします。

定義出来る動作としては以下の単位が考えられます。 細かく設定することも可能です。運用に合わせた単位で設定します。

  • pull request 単位
  • release tag 単位
  • 定期実行

前述した項目を盛り込みCI/CDパイプラインを作成しましょう。 シンプルなパイプラインからはじめ、必要に応じてステージを追加していきましょう。

Jenkins AgentをKubernetes上で実行できるようにする

Jenkinsからkubernetes上でJenkins agentを実行する場合にはJenkins kubernetes-plugin の導入が必要です。 通常はソースコードの取得から実施することになります。gitを使う場合であればgitのjenkins-pluginが必要です。

本ガイドで準備した values.yaml を使用している場合にはすでにどちらも導入されている状態となります。

ここでは Jenkins から kubernetesへ接続できるようにする設定を提示いたします。

Jeninsログイン後、クレデンシャルを事前に作成します。

_images/Kubernetes-Credentials.png

jenkins 導入済みのネームスペースにサービスアカウントを作成します。

kubectl create clusterrolebinding jenkins --clusterrole cluster-admin --serviceaccount=jenkins:default

Configurationから「Kubernetes設定」を選択します。

_images/jenkins-configuration.jpg

ここでは必要となるパラメータを設定していきます。

  • kubernetes URL: マスタのIP, 192.168.XX.10
  • Kubernetes Namespace: Jenkinsをインストールしたnamespace名
  • kubernetes certificate key: /etc/kubernetes/pki/apiserver.crtの内容をペースト
  • Credentials: Secret を選択
Jenkins Pipelineの作成
  • テスト実行
  • アプリケーションビルド
  • コンテナイメージのビルド
  • レジストリへコンテナイメージのpush
  • アプリケーションデプロイ

上記のようなパイプラインを作成にはJenkins pipeline機能が活用できます。

ここではテンプレートを準備しました、上記の様なパイプラインを実装してみましょう。 Jenkins ではパイプラインを構築するために2つの記述方法があります。

それぞれの違いついてはこちら。

Jenkins pipelineのフォーマット
pipeline {
    agent {
        kubernetes {
            label 'jenkins-ci'
            defaultContainer 'jnlp'
            yamlFile 'KubernetesPod.yaml'
        }
    }

    stages {

        stage('Pre Build Check') {
            steps {
                script {
                    echo "Pre Build staget implement!"
                }
                container('busybox') {
                    sh 'echo Hello Container World in Pre Build Check'
                }
            }
        }

        stage('Build') {
            steps {
                echo "Build staget implement!"
                script {
//                    Dockerfile 内部でdockerを導入する
//                    docker.build("nodejs-build-${env.BUILD_ID}").inside() {
//                        node -v
//                    }
                }
            }
        }

        stage('Test') {
            steps {
                echo 'Test Stage implement!'
                container('kubectl') {
                    sh 'kubectl version'
                }
            }
        }

        stage('Deploy') {
            steps {
                echo 'printenv'
                container('helm') {
                    sh 'helm version'
                }
            }
        }
    }
}
Jenkins pipelineをkubernetesで動作させるコンテナのテンプレートを定義
metadata:
  labels:
    some-label: jenkins-ci
spec:
  containers:
  - name: jnlp
    env:
    - name: CONTAINER_ENV_VAR
      value: jnlp
  - name: busybox
    image: busybox
    command:
    - cat
    tty: true
    env:
    - name: CONTAINER_ENV_VAR
      value: busybox
  - name: kubectl
    image: lachlanevenson/k8s-kubectl:v1.8.8
    command:
    - cat
    tty: true
    env:
    - name: CONTAINER_ENV_VAR
      value: kubectl
  - name: helm
    image: lachlanevenson/k8s-helm:latest
    command: 
    - cat
    tty: true
    env:
    - name: CONTAINER_ENV_VAR
      value: helm

Jenkins pipeline の作成が完了したら任意のGitリポジトリにpushします。 以降のJenkins Pipelineの実行にJenkinsfileを使用します。

アプリケーションの変更を検知してデプロイメント可能にする

CI/CDのパイプラインを作成したら実際にアプリケーションの変更をトリガー(ソースコードの変更、Gitリポジトリへのpush等)としてk8sへアプリケーションをデプロイします。

ポリシーとして大きく2つに別れます、参考までに以下に記載いたします。

  • デプロイ可能な状態までにし、最後のデプロイメントは人が実施する(クリックするだけ)
  • デプロイメントまでを完全自動化する

実際にkubernetes環境へのデプロイができたかの確認とアプリケーションが稼働しているかを確認します。

今回はサンプルとしてJenkinsのBlueOcean pluginを使用してPipelineを作成します。

_images/jenkins_blueocean.png

BlueOcean plugin を使用するとウィザード形式でPipelineを作成することができます。

各入力値については以下のURLにてどのような形式で入力されるかの記載があります。

コンテナをCI/CDする方法 Helmを使ってみる

コンテナのCI/CDではいくつか方法があります。 ここではコンテナをCI/CDするために必要な検討事項を記載するとともに

個別のアプリケーションデプロイメントからHelm Chartを使ったデプロイメントに変更します。

作成したコンテナをHelm Chartを使ってデプロイするようにします。

Helm Chartの開発ガイドは以下のURLを確認ください。

他にも以下のようなCI/CDを行いやすくする構成管理・パッケージマネジメントのツールが存在しています。

  • Kustomize
  • Draft
  • GitKube
  • Skaffold
デプロイメントのさらなる進化

CI/CDプロセスを成熟させていくと常にリリース可能な状態となっていきます。 そのような状態になると本番環境へのデプロイを迅速にし、ダウンタイムを最小化するための方法が必要になってきます。 元々存在するプラクティスや考え方となりますがコンテナ技術、kubernetesのスケジューラー機能を使うことで今までの環境とくらべて実現がしやすくなっています。

Blue/Greenデプロイメント, Canary リリースというキーワードで紹介したいと思います。

Level 4: 運用編 , Level 5: Microservice化 で登場するサービスメッシュ、Istioの機能で実現できます。

また、NetAppが提供しているNetApp Kubernetes ServiceでもKubernetesクラスタのデプロイから、Istioを使ったルーティングを視覚的に操作できる機能を提供しています。 詳細は アプリケーションスタック、Kubernetes スタックの管理 で章を設けます。

ちなみに

CDには2つの意味を含んでいるケースがあります。文脈に応じて見分けるか、どちらの意味か確認しましょう。

  • Continuous Deployment: 常にデプロイ可能なものを生成するまでを自動化する、最後のデプロイメントは手動で実施。
  • Continuous Delivery: 本番環境へのデプロイメントまでを自動化する。
Blue/Greenデプロイメント

従来のやり方では1つの環境にデプロイし何かあれば戻すという方法をほとんどのケースで採用していたかと思いますが、さらなる進化として常に戻せる環境を準備し迅速にロールバック 新バージョン、旧バージョンをデプロイしたままルータで切り替えるようになります。

様々な企業で行き着いている運用でもあるかと思いますが、2010年にBlueGreenデプロイメントという名称で説明しています。

実現方法、切り替えのタイミングなどあり、BlueGreenの実装の決定的なものはなく、1つのプラクティスとして存在しています。

2つの環境を準備し、どこかのタイミングで切り替えを行うためDBのマイグレーションの方法などを検討する必要はでてきます。

Canary

Canary リリースは BlueGreen デプロイメントと類似したデプロイメントになります。 Blue/Green デプロイメントはすぐに古いバージョンにもどせるように仕組みを整えたものですが、Canaryリリースは新しいバージョン、旧バージョンにアクセスする比率を決めてデプロイするプラクティスです。

こちらは2つの環境ではなく、1環境に複数バージョンのアプリケーションが存在することになります。そのためDBのデータをどのように取り扱うかは検討が必要となります。

まとめ

このラボではコンテナ化したアプリケーションのCI/CDパイプラインの構築に挑戦しました。 CI/CDパイプラインを作成するためのJenkins/GitLabをインストールするために必要なHelmが使えるようになりました。

本ラボでは簡易的なパイプラインを実際に構築しました。パイプライン内の処理については個々で実装したものから発展させ様々な処理を追加することができます。

ここまでで Level3 は終了です。

Level 4: 運用編

目的・ゴール: 運用を行うにあたり必要となる検討事項を把握する

本番運用になった場合に必要となるアプリケーションの可用性、インフラの可用性、非機能要件の実現について検討します。

Level3まででアプリケーションを迅速にリリースする仕組みを作成しました。ここからはマスタのHA化やバックアップをどうするかを検討します。

流れ

本レベルでは運用時に課題となり、解決が必要となる項目についてリストしました。 現在の環境に対して変更をして見てください。ここでは実際に手を動かしてもらってもいいですし、 チーム内でディスカッションの材料にしてください。

アプリケーションの可用性を向上させる
アプリケーションの可用性を挙げるためWorkload APIを使用する

すでに Deployment で使われているかもしれませんが、replica数などを定義できます。一般的なアプリケーションデプロイに使用します。

各ノードでコンテナを稼働させる DaemonSet があります。ログ収集用のデーモンやメトリクス収集などのユースケースがあります。

レプリカ生成時の順序制御、各ポッドにPVを割り当てることができる StatefulSet があります。主にクラスタ、分散環境におけるユースケースで使用するものです。

kubernetes上のオブジェクト名は以下の通りです。

  • ReplicaSet
  • DaemonSet
  • StatefulSet
ローリングアップデート

DeploymentPod template 部分に変更があった場合に自動でアップデートできます。

$ kubectl set image deployment/DEPLOYMENT CONTAINER=IMAGE_NAME:TAG

--record オプションをつけるとアノテーションが付与されます。

参考: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/

リリース後に問題発生、アプリケーションを戻す

アプリケーションは Deployment でリビジョン管理しています。

rollout history でリビジョンを確認できます。

$ kubectl rollout history deployment/デプロイメント名
deployments "nginx-deployment"
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

各リビジョンの詳細については --revision=N を付与することで詳細を確認できます。

$ kubectl rollout history deployment/nginx-deployment --revision=2
deployments "nginx-deployment" with revision #2
Pod Template:
  Labels:       app=nginx
        pod-template-hash=1520898311
  Containers:
   nginx:
    Image:      nginx:1.9.1
    Port:       80/TCP
    Environment:        <none>
    Mounts:     <none>
  Volumes:      <none>

アプリケーションは Deployment でリビジョン管理しており、ロールバック機能も提供しています。 rollout undo で直前のリビジョンに戻ります。--to-revision を指定することで任意のリビジョンに戻すことも可能です。

$ kubectl rollout undo deployment/nginx-deployment [--to-revision=N]

保存されるリビジョンは revisionHistoryLimit で定義できるため、運用に合わせた数にしましょう。

Helmを使った場合にも同様のことが実現可能です。

アプリケーション負荷に応じたスケールアウト・イン

Horizontal Pod Autoscaler を使用してアプリケーションの負荷に応じてスケールアウトすることができます。

事前定義としてアプリケーションの負荷情報をheapsterで収集しておく必要があります。 以下の例はすべてのポッドのCPU使用率の平均が50%を超えた場合にレプリカを最大10まで増やす動作をします。

$ kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10

上記の例では、CPU使用率をメトリクスとしていますが複数のメトリクスを使用したり、カスタマイズすることも可能です。

参考: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/

アプリケーション負荷に応じたスケールアップ

Horizontal Pod AutoScaler に対して Vertical Pod AutoScaler があります。

完全互換ではありませんが、Vertical Pod AutoScalerというものが k8s 1.9でalpha versionとして提供されています。 従来型のアプリケーションではスケールアウトより、スケールアップのほうが行いやすいのが一般的です。

https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler

アプリケーションの監視

kubernetsで監視すべき項目としてはクラスタ全体の監視とアプリケーションごとの監視になります。

  • クラスタ全体の監視については後述します。
  • 稼働しているアプリケーションの監視(Pod の監視)
Kubernetes クラスタの操作性を向上させる
Rancher でできること

RancherはブラウザーのUIを通したグラフィカルなインターフェースを持っており、様々な環境のkubernetesクラスタを管理するとともに、コンテナーの管理、アプリケーションの管理も行うことができます。ここでいうアプリケーションは kubernetes 上で動くものすべてです。

Rancherの機能については、以下のサイトからご確認ください。

Your Enterprise Kubernetes Platform | Rancher Labs https://rancher.com/

ここではRancherの導入から、アプリケーションのデプロイを実施します。 アプリケーションとして、kubernetes クラスタを監視するソフトウェアスタック(Prometheus+Grafana)をkubernetes上で簡単に起動してみます。

Rancher を導入する
dockerをインストールする

Rancherの導入には、Dockerコマンドを利用します。もし、Dockerをインストールしていない場合にはDockerをインストールします。

Rancherに必要なDockerのバージョンは、以下のURLに書いてあります。 https://rancher.com/docs/rancher/v2.x/en/installation/requirements/

  • 1.12.6
  • 1.13.1
  • 17.03.2

となっていますが、18.06.01 でも動いています。今回は、18.06を使います。

インストール方法は、

curl https://releases.rancher.com/install-docker/18.06.sh | sh

でインストールしてください。

Rancherをインストールする

次にRancherをインストールします。

以下のDockerHubのタグでv2.x系の最新のバージョンを確認してください。。

今回は、v2.2.6 をインストールします。

docker run -d --restart=unless-stopped \
-p 80:80 -p 443:443 \
rancher/rancher:v2.2.6
Rancher へログイン

上記のRancherをインストールしたホストのIPアドレスでブラウザーを開くと以下のような画面が表示されます。

_images/Login.png

パスワードを指定するか、ランダムのパスワードを生成して Continue を押します。

Kubernetes クラスターのインポート

次に、作っておいた Kubernetesクラスターを Rancherから認識できるようにインポートします。 Globalから Add Cluster ボタンを押します。

_images/Add-Cluster-Dashboard.png

クラスター追加画面が出てきますが、右上の IMPORT ボタンを押します。

_images/Import-Cluster.png

次に、Cluster Nameを指定して Create ボタンを押します(Memberは自分一人で使う分には追加する必要はありません)。

_images/Set-ClusterName.png

以下のページで表示されたコマンドを実行します。 kubectlコマンドは事前にインストールし、kubernetesに接続できるよう設定しておいてください。

_images/Import-command.png
kubectl create clusterrolebinding cluster-admin-binding --clusterrole cluster-admin --user [USER_ACCOUNT]

上記の [USER_ACCOUNT] は上記コマンドを実行するユーザーIDを指定します。

kubectl apply -f https://xxxxxxxxxxxxxx.com/v3/import/XXXXXXXXXXXXXXXXXXXXXXXXX.yaml

上記のコマンドで証明書の問題のエラーが発生する場合は、以下のコマンドを実行して下さい。

curl --insecure -sfL https://xxxxxxxxxxxxxx.com/v3/import/XXXXXXXXXXXXXXXXXXXXXXXXX.yaml | kubectl apply -f -

KubernetesクラスターがRancherにインポートされると以下のようにGlobalのClusterダッシュボードにインポートされたクラスターが表示されます。

_images/cluster-list.png
アプリケーションをデプロイ
Prometheus+Grafanaのデプロイする

上記、クラスターがインポートされた状態でPrometheus+Grafanaをデプロイしてみましょう。 まず、インポートされたKubernetesクラスターのDefaultネームスペースに切り換えます。

_images/change-name-default.png

Global を押してドロップダウンしたメニューの Default をクリックします。 ワークロードのダッシュボード画面に切り替わります。

_images/cluster-default-dashboard.png

この画面の Catalog Apps をクリックします。

_images/CatalogApp-list.png

カタログリストから 右側の Search 検索ボックスに Prometheus を入力します。

_images/CatalogApp-Prometheus.png

View Details をクリックします。 様々な設定項目がありますが、Grafana Admin Password だけ任意のパスワード入力します。

_images/Settings-Prometheus-Grafana.png

デプロイが開始されると以下のような画面になります。

_images/Deployed-Prometheus.png

Prometheusをクリックします。

_images/Prometheus-Details.png

上記の Workloads を確認します。

_images/Workloads-prometheus.png

prometheus-grafana の80/http をクリックします。

_images/Grafana-Dashboard.png

画面が表示されれば正常にデプロイされています。

インフラの可用性を向上させる
k8s Master の冗長化

API受付をするマスタ系のノードやetcdやkubernetesサービスの高可用性も担保しましょう。

また、障害設計をどの単位(DC単位、リージョン単位)で行うかも検討をしましょう。

ログの確認、デバッグ方法

標準のkubectlだとログが追いづらいときがあるため以下のツールの検討をします。

  • kubernetesホストの/var/log/containerにログは保管。(systemd系の場合)
  • sternなどのログ管理ツールを活用する
  • fluentdを使いログを集約する。
コンテナクラスタの監視

監視する対象として、メトリクス監視、サービス呼び出し、サービスメッシュなど分散環境になったことで従来型のアーキテクチャとは違った監視をする必要があります。 簡単にスケールアウトできる=監視対象が動的というような考え方をします。

また、分散環境では1つのアプリケーションでも複数のサービス呼び出しがおこなわれるため、どのようなサービス呼び出しが行われているかも確認できる必要があります。

  • (heapster|Prometheus) + Grafana + InfluxDB を使いコンテナクラスタの監視。
  • 分散環境に於けるサービスの呼び出しを可視化 Traces = Zipkin
  • ServiceGraph Graphbiz & Prometeus
  • ServiceMesh

Helmで提供されているGrafana+Prometheusをデプロイし監視することにチャレンジしてみましょう。

Helmで監視システムを簡単デプロイ

Prometheusは長期保管はできないため長期保管の際は別途保管が必要です。

バックアップはどうするか?

大きく以下のバックアップ対象が存在します。

  • etcd
  • コンテナ化されたアプリケーションの永続化データ
  • コンテナイメージ
etcd のバックアップ戦略

kubernetes の構成情報が保存されているetcdのバックアップをどうするかについてわかりやすいドキュメントが公開されています。 基本方針としては、etcdctlのスナップショットを定期的にとる、またはストレージ機能でとるという2つの大きな方針です。

参考までに etcdctl を使ったサンプルを提示します。

ETCDCTL_API=3 etcdctl --debug --endpoints https://ip_address:2379 --cert="server.crt" --key="server.key" --cacert="ca.crt" snapshot save backup.db

この実行内容をCronJob等で定期的に取得し保管するという方法が取れます。

コンテナ化されたアプリケーションの永続化データ

基本方針として永続化するデータは外部ストレージに保管するという方針でいくと、 ストレージ側でバックアップを取得するのが比較的容易にバックアップ可能です。

ご参考までに、trident ではストレージスナップショットを定期的に取得するよう設定可能です。 1サイトでのバックアップは簡易的に可能ですが、遠隔地保管等をする場合は後述の「DRをどうするか?」で言及します。

コンテナイメージ

上記2つとは考え方が違うものになります。 クラウドのサービスを使う上では可用性や冗長性はSLAに従うことになり、ユーザ側で意識することはあまりありません。 プライベートレジストリを利用する場合は、ダウンしてしまうと新たにアプリケーションがデプロイできないという自体になってしまいます。

セキュリティアップグレード

例えば、脆弱性があった場合の対処方法はどうすればよいか。

  • ノードごとにバージョンアップするため、ある程度の余力を見込んだ設計とする。
  • kubectl drain を使いノードで動いているPodを別ノードで起動、対象ノードをアップデートし、ポッドを戻す。
DRをどうするか?

アプリケーションのポータビリティはコンテナで実現。 別クラスタで作成されたPVはそのままは参照できないので以下の方法を検討する。

  • CSI (Container Storage Interface)の既存ボリュームのインポートに対応をまつ
  • Heptio ark: https://github.com/heptio/ark + SVM-DR
Podが停止した場合のアプリケーションの動作

Dynamic ProvisioningされたPVCのPod障害時の動作については以下のような動作になります。 PVCはTridentを使ってデプロイしたものです。

  • 停止したPodは別ノードで立ち上がる
  • Podからマウントしていたボリュームは再度別ノードでもマウントされデータの読み書きは継続可能

Stateful Set を使い、MongoDBを複数ノードで構成し上記の検証を行った結果が以下のリンク先で確認できます。

Statefulset を使った障害時の動作

アプリケーションスタック、Kubernetes スタックの管理
複数環境下でのアプリケーション/インフラスタックの管理

ここでは複数環境下でアプリケーションからkubernetesクラスタ、データまでを管理することを実現するための一つの方法として、 NetApp Kubernetes Service を紹介します。

NetApp Kubernetes Service (NKS)

Level 5: Microservice化

目的・ゴール: Microserviceを支えるインフラの技術・テクノロジースタックを活用

Level4までで既存のアプリケーションをコンテナ化して本番運用可能かつ迅速にリリースできる仕組みを作りました。 このレベルではアプリケーションをコンポーネント単位に分割していった際に必要となるインフラの仕組みを適用します。

また、Microserviceを実現するServiceMesh、”Istio”を体験します。

流れ
  1. Microservice化していくにあたって発生する課題
  2. 解決する技術の1つ "Istio"
Microservice化していくにあたって発生する課題

マイクロサービス化が進むと1つのシステムを複数の細かいコンポーネントにわけ、独立したサービスとして迅速にデプロイできるようになり、

その反面、モノリスなアプリケーションでは問題とならなかったサービス間の接続、モニタリング、通信の制御、エンドツーエンドの認証などが課題として顕在化してきます。

マイクロサービスを実現するためには上記の課題を解決する必要があります。

解決する技術の1つ "Istio"

ここでは上記の課題を解決するための"Istio"について紹介します。

モノリスなアプリケーションから分散型・コンポーネントに分割されたアプリケーションへコンポーネント分割際に発生する課題に対して有効です。

最初はモノリスなアプリケーションをそのままコンテナ化し徐々にコンポーネントに分けていく、そしてサービス間通信をうまくやるためのServiceMeshとして開発されました。

ポイントはアプリケーションに影響なくIstioを導入できることです。

ここでは簡単にIstioを体験してみます。

Istio を体験してみる

マイクロサービスの参考アーキテクチャ

Microserviceのアプリケーションを様々なテクノロジー、言語を使って実現しているサンプルがあります。 このサンプルでは様々なスタックで必要とされるテクノロジーが網羅されているため参考になります。

コンピューティングの最適化

サーバレスアーキテクチャというキーワードが注目されています。

AWSであればLambda, AzureであればDurableFunction, GCPであれば Cloud Functions といった各クラウドプロバイダーがそれぞれサービスを提供しています。

もちろんKubernetes上でも同様のサーバレスアーキテクチャを実現するものがあります。

Knative というものが存在しており、Helm、Istioで構成されておりセットアップは最小でできるようになっています。

さらなる進化

マルチクラウド、ハイブリッドクラウドは一部実践編で体験することができます。

マルチクラウド化にはIstioのCrossClusterMesh等を使った方法でも実現が可能です。

全般的にネットワークの設計をどうするかという課題が残ります。

  • マルチクラウド化
  • ハイブリッドクラウド化

実践編: はじめに

概要

Kubernetes上でAIアプリケーションを作成するハンズオンです。 まずは一連の流れを体験することが目的です。

ハンズオンの流れ
  • 当日の環境確認
  • ハンズオンを行うための環境構築
  • トレーニングデータの準備
  • サンプルの学習コードを使ってモデル作成
  • アプリケーションから利用するためのモデルを生成
  • 生成したモデルを生成し、クライアントから呼び出し、推論を行う
ゴール・目的
基本コース

Kubernetes環境下でAIアプリケーションのデプロイまでの1連の流れを体験する。

  • データの取得から、データ準備、トレーニング、サーブまでを体験

最終的なアウトプットとしては以下のように、物体に写真の中の物体にマーキングされた画像を出力するのがゴールです。

_images/image1_output.jpg
オプション

環境・リソースに限りがあるため終わった方で試したい方はお伝えください。

  • オプションでフローの中を更に高速化
    • GPUを活用し演算の高速化を体験
    • KubeflowのコンポーネントであるArogo CI を使い自動化を体験
  • メインはオンプレの環境を使用しましたが、これがクラウドでもアーキテクチャの変更なしに同じことができることを体験する。

ハンズオンのための環境構築と確認

目的・ゴール: Kubeflowのインストールと環境の確認

最初に今回の環境の確認とハンズオンを行うために使う Kubeflow のインストールを行います。

このセクションのゴールはハンズオンの環境が問題ないことの確認とKubeflowのインストールを完了させることです。

今回の環境の全体概要
環境
  • Kubernetes 1.13 オンプレミス(構築済み)
  • Kubernetes 1.13 GPU クラスタ(構築済み): 主にトレーング時にクラスタを切り替えて利用
  • Kubeflow オンプレミス: ハンズオンでインストール
  • Kubernetes 1.12 Google Cloud Platform : Kubeflowをインストール済み、アプリケーションのサーブ時に使用
環境の確認

自身に与えられた環境にログインできるかを確認します。

以下のログイン先は今回使用するKubernetesクラスタのマスタノードです。

  • xxx: 自身の番号
$ ssh localadmin@192.168.xxx.10
$ kubectl get node

上記の kubectl get node で複数のノードが出力されることを確認してください。

Tridentのインストール

ここでは基礎編を参照しTridentの導入をしましょう。

Level 2: ステートフルコンテナの実現 を参照してダイナミックストレージプロビジョニングを設定しましょう。

以下の項目を設定し、 ontap-gold を作成します。

  • NetApp Tridentのインストール
  • StorageClassの定義

ハンズオン簡易化のため作成したストレージクラスをデフォルトとします。

$ kubectl patch storageclass ontap-gold -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

実行後以下の表記となっていたら完了です。

$ kubectl get storageclass

NAME                 PROVISIONER            AGE
ontap-gold (default) netapp.io/trident      3d15h
Kubeflow のインストール

Kubeflowのインストールを続けます。

Kubeflowを導入するために使う ksonnet のバージョンを確認します。

$ ks version

ks version の結果が ksonnet version: 0.13.1 であることを確認してください。

Kubeflowのインストールを開始します。

Kubeflowのインストールユーティリティである kfctl.sh をダウンロードします。

kubeflow_src を作成し作業ディレクトリとします。

$ cd
$ mkdir kubeflow_src
$ cd kubeflow_src
$ export KUBEFLOW_TAG=v0.4.1
$ curl https://raw.githubusercontent.com/kubeflow/kubeflow/${KUBEFLOW_TAG}/scripts/download.sh | bash

kubeflowがダウンロードできたことを確認します。

$ ls -F

deployment/ kubeflow/       scripts/

kfctl.sh init デプロイメント名 でセットアップ、デプロイを実施します。

デプロイメント名は以下のサンプルでは kubeflow-deploy としますが任意の名称です。

kubeflow-deploy フォルダが作成され、その配下にデプロイメント用のファイルが作成されます。

$ scripts/kfctl.sh init kubeflow-deploy --platform none
$ ls -F

    deployment/     kubeflow/       kubeflow-deploy/        scripts/

kubeflow-deployディレクトリが作成されました。

インストールを続けます。以下の作業を実施します。

$ cd kubeflow-deploy/
$ ../scripts/kfctl.sh generate k8s

生成された設定をそのままapplyするとambassador等UIを提供するサービスはClusterIPで公開されます。 外部からはアクセス出来ませんのでサービスのタイプを変更します。

注釈

下記ではNodePortに変更していますが、ラボの環境ではLoadBalancerを使う事も可能です。 また、公開は必須ではなくkubectlを動作させている端末上のポートにフォワードして uiを使う事も可能です。 また、JupyterについてはAmbassador上からアクセスする事が可能ですので必須ではありません。

$ cd ks_app/
$ ks param set ambassador ambassadorServiceType NodePort
$ ks param set jupyter serviceType NodePort
$ cd ..

設定が完了したら適用してKubernetesに投入します。

$ ../scripts/kfctl.sh apply k8s

ここまででデプロイが完了です。

どのようなコンポーネントがデプロイされたかを確認しましょう。

DESIRED 列と AVAILABLE 列の数字が同一であれば正常に可動している状況です。

$ kubectl get deploy -n kubeflow

NAME                                     DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
ambassador                               3         3         3            3           49m
argo-ui                                  1         1         1            1           48m
centraldashboard                         1         1         1            1           49m
katib-ui                                 1         1         1            1           26m
minio                                    1         1         1            1           27m
ml-pipeline                              1         1         1            1           27m
ml-pipeline-persistenceagent             1         1         1            1           27m
ml-pipeline-scheduledworkflow            1         1         1            1           27m
ml-pipeline-ui                           1         1         1            1           27m
mysql                                    1         1         1            1           27m
pytorch-operator                         1         1         1            1           48m
spartakus-volunteer                      1         1         1            1           48m
studyjob-controller                      1         1         1            1           26m
tf-job-dashboard                         1         1         1            1           49m
tf-job-operator-v1beta1                  1         1         1            1           49m
vizier-core                              1         1         1            1           26m
vizier-core-rest                         1         1         1            1           26m
vizier-db                                1         1         1            1           26m
vizier-suggestion-bayesianoptimization   1         1         1            1           26m
vizier-suggestion-grid                   1         1         1            1           26m
vizier-suggestion-hyperband              1         1         1            1           26m
vizier-suggestion-random                 1         1         1            1           26m
workflow-controller                      1         1         1            1           48m

minio/mysql/vizier-dbはDB等の永続化ボリューム(Persistent Volume)を必要とします。 ボリュームの状態を確認します。

STATUS 列が Bound と表示されていることを確認してください。

また、バージョンによっては出力結果が異なる可能性があります。

$ kubectl get pvc -n kubeflow

NAME             STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
katib-mysql      Bound    vol3     10Gi       RWO                           73s
minio-pv-claim   Bound    vol1     10Gi       RWO                           89s
mysql-pv-claim   Bound    vol2     10Gi       RWO                           89s

$ kubectl get pv

NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS   REASON   AGE
vol1   10Gi       RWO            Retain           Bound    kubeflow/minio-pv-claim                           3m17s
vol2   10Gi       RWO            Retain           Bound    kubeflow/mysql-pv-claim                           3m17s
vol3   10Gi       RWO            Retain           Bound    kubeflow/katib-mysql                              3m17s

注釈

Tridentの設定が終わっていない場合、永続化ボリュームがプロビジョニングされず コンテナが起動できません。Tridentの導入と、デフォルトストレージクラスの設定まで を完了させてください。

まとめ

ここまでの手順で今回のハンズオンで使うkubeflow環境の構築を完了しました。

kubeflowはマシンラーニングのためのプラットフォームです。 マシンラーニングを実行するためのパイプラインをkubernetes上で実行するためのコンポーネント群を提供します。 シンプル、ポータブル、スケーラブルという特徴があり、Kubernetes上であればどこでも稼働させることができます。

KubeflowにはJupyterNotebook、Katib(ハイパーパラメタチューニング)、 バッチ処理のためのフレームワーク、エンドツーエンドのパイプライン、様々なフレームワーク(PyTorch、MXNet、Chainer、TensorFlow)が含まれており、 マシンラーニングを実行するための基盤を提供しています。

本ハンズオンではコマンドラインからフェーズごとにコンテナを活用してジョブ投入を行い一連ワークフローを体験いただきます。

なお、本ハンズオンではシェル内で変数を定義していきます。 もし何らかの原因でシェルのセッションが切れるようなことがあった場合にはいかに一覧がありますので ここを参照してください。

補足:利用変数一覧
ENV=default
PVC="pets-pvc"
MOUNT_PATH="/pets_data"
DATASET_URL="http://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz"
ANNOTATIONS_URL="http://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz"
MODEL_URL="http://download.tensorflow.org/models/object_detection/faster_rcnn_resnet101_coco_2018_01_28.tar.gz"
PIPELINE_CONFIG_URL="https://raw.githubusercontent.com/kubeflow/examples/master/object_detection/conf/faster_rcnn_resnet101_pets.config"
ANNOTATIONS_PATH="${MOUNT_PATH}/annotations.tar.gz"
DATASET_PATH="${MOUNT_PATH}/images.tar.gz"
PRE_TRAINED_MODEL_PATH="${MOUNT_PATH}/faster_rcnn_resnet101_coco_2018_01_28.tar.gz"
OBJ_DETECTION_IMAGE="userXX/pets_object_detection:1.0"
PIPELINE_CONFIG_PATH="${MOUNT_PATH}/faster_rcnn_resnet101_pets.config"
TRAINING_DIR="${MOUNT_PATH}/train"
CHECKPOINT="${TRAINING_DIR}/model.ckpt-<Number>" #replace with your checkpoint number
INPUT_TYPE="image_tensor"
EXPORT_OUTPUT_DIR="${MOUNT_PATH}/exported_graphs"
DATA_DIR_PATH="${MOUNT_PATH}"
OUTPUT_DIR_PATH="${MOUNT_PATH}"
MODEL_COMPONENT=pets-model
MODEL_PATH=/mnt/exported_graphs/saved_model
MODEL_STORAGE_TYPE=nfs
NFS_PVC_NAME=pets-pvc

データの探索: データを扱ってみる

目的・ゴール: まずは取り扱うデータを準備する

まずはトレーニングを実行するために必要なデータを準備します。

データを置く場所であるPersistentVolumeClaimを作成しデータをダウンロード解凍を実施します。

概要

ここでは以下のフローで進んでいきます。 一覧としては以下の通りです。一つ一つの用語がわからない場合があるかもしれませんがまずは動かし体験することを目標としています。

  1. テスト、トレーニングデータ、トレーニング結果(モデル)を保管するPersistentVolumeClaimをデプロイする
  2. データセットのダウンロード、データセットのアノテーション、事前トレーニング済みモデルチェックポイント、トレーニングパイプラインの構成ファイルをダウンロード
  3. ダウンロードしたデータ・セット、事前トレーニング済みデータ・セット、データ・セットアノテーションの解凍
  4. ペット検知器モデルをトレーニングするので、TensorFlowペットレコードを作成

ハンズオンではksonnetというツールを使用し進めます。

https://ksonnet.io/

この例で使用する一連のコンポーネントを含むksonnetアプリ ks-app が存在します。 コンポーネントは ks-app/components ディレクトリにあります、カスタマイズしたい場合はここを編集します。

前章から続けている場合はKubeflowのディレクトリに移動しているため、一旦ホームに戻り、今回のお題の画像解析AI用のディレクトリに移動します。 なお、このサンプルアプリケーションはベースとしてKubeflowのExampleを使用しております。

まずはサンプルコードが含まれているリポジトリをクローンします。

$ cd
$ git clone https://github.com/NetAppJpTechTeam/examples.git

クローンが完了したらサンプルアプリ(物体認識)のディレクトリに移動し、設定を実施します。

$ cd examples/object_detection/ks-app
$ export ENV=default
$ ks env add ${ENV} --context=`kubectl config current-context`
$ ks upgrade
$ ks env set ${ENV} --namespace kubeflow
トレーニングデータの準備

作業ディレクトリで pwd を実行し以下のディレクトリであることを確認しましょう。

$ pwd

/home/localadmin/exmaples/object_detection/ks-app
データ保管用の領域を作成

データを保管するPersistentVolumeClaim(PVC)を作成します。

ハンズオンではダイナミックストレージプロビジョニングが必要となります。 前章でインストール、設定したTridentを使用します。

ksonnet のコンポーネントを編集します。

$ ks param set pets-pvc accessMode "ReadWriteMany"
$ ks param set pets-pvc storage "20Gi"
$ ks param set pets-pvc storageClassName "ontap-gold"

ここまでで上記でセットしたパラメータを確認しましょう。

$ ks param list pets-pvc

COMPONENT PARAM            VALUE
========= =====            =====
pets-pvc  accessMode       'ReadWriteMany'
pets-pvc  name             'pets-pvc'
pets-pvc  storage          '20Gi'
pets-pvc  storageClassName 'ontap-gold'
pets-pvc  volumeMode       'Filesystem'

展開したファイルだと、StorageClassを定義する項目を追加しています。

$ cat components/pets-pvc.jsonnet

storageClassName: params.storageClassName が追記されている場所を確認し、この内容が追加されることで実現できることを考えてみましょう。

以下のファイルとなっていれば完了です。

local env = std.extVar("__ksonnet/environments");
local params = std.extVar("__ksonnet/params").components["pets-pvc"];

local k = import "k.libsonnet";

local pvc = {
  apiVersion: "v1",
  kind: "PersistentVolumeClaim",
  metadata:{
    name: params.name,
    namespace: env.namespace,
  },
  spec:{
    accessModes: [params.accessMode],
    volumeMode: params.volumeMode,
    resources: {
      requests: {
        storage: params.storage,
      },
    },
    storageClassName: params.storageClassName
  },
};

以下のコマンドを実行するとデータ保管用の領域であるPVCが作成されます。

$ ks apply ${ENV} -c pets-pvc

INFO Applying persistentvolumeclaims kubeflow.pets-pvc
INFO Creating non-existent persistentvolumeclaims kubeflow.pets-pvc

以下のコマンドを実行し、Statusが「Bound」となっていれば完了です。

$ kubectl get pvc pets-pvc -n kubeflow

NAME       STATUS   VOLUME                    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pets-pvc   Bound    kubeflow-pets-pvc-e2be6   20Gi       RWX            ontap-gold     6m55s

ここまででデータを保管するPVCが作成できたため、次はPVCに必要なデータをダウンロードします。

AI作成に必要なデータをダウンロード

ここまでに作成した pets-pvc へデータをダウンロードし保管します。

変数定義を実施します。

$ PVC="pets-pvc"
$ MOUNT_PATH="/pets_data"
$ DATASET_URL="http://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz"
$ ANNOTATIONS_URL="http://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz"
$ MODEL_URL="http://download.tensorflow.org/models/object_detection/faster_rcnn_resnet101_coco_2018_01_28.tar.gz"
$ PIPELINE_CONFIG_URL="https://raw.githubusercontent.com/kubeflow/examples/master/object_detection/conf/faster_rcnn_resnet101_pets.config"

ksonnetにパラメータを指定します。

$ ks param set get-data-job mountPath ${MOUNT_PATH}
$ ks param set get-data-job pvc ${PVC}
$ ks param set get-data-job urlData ${DATASET_URL}
$ ks param set get-data-job urlAnnotations ${ANNOTATIONS_URL}
$ ks param set get-data-job urlModel ${MODEL_URL}
$ ks param set get-data-job urlPipelineConfig ${PIPELINE_CONFIG_URL}

指定したパラメータを確認します。

$ ks param list get-data-job

COMPONENT    PARAM             VALUE
=========    =====             =====
get-data-job mountPath         '/pets_data'
get-data-job name              'get-data-job'
get-data-job pvc               'pets-pvc'
get-data-job urlAnnotations    'http://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz'
get-data-job urlData           'http://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz'
get-data-job urlModel          'http://download.tensorflow.org/models/object_detection/faster_rcnn_resnet101_coco_2018_01_28.tar.gz'
get-data-job urlPipelineConfig 'https://raw.githubusercontent.com/kubeflow/examples/master/object_detection/conf/faster_rcnn_resnet101_pets.config'

ここで使用しているサンプルの一部ではkubernetesクラスタ内から外部への名前解決が失敗する状態になっています。 同じ動作をするコンテナイメージを作成しましたので以下のファイルの image の部分を変更してください。

image: "inutano/wget" から image: "makotow/wget:dns-fix-0.1.2"へ変更してください。

$ vim components/get-data-job.jsonnet

最終的にファイル全体が以下のようになっていれば完了です。

local env = std.extVar("__ksonnet/environments");
local params = std.extVar("__ksonnet/params").components["get-data-job"];

local k = import "k.libsonnet";

local getDataJob(namespace, name, pvc, url, mountPath) = {
      apiVersion: "batch/v1",
      kind: "Job",
      metadata: {
        name: name,
        namespace: namespace,
      },
      spec: {
        template: {
          spec: {
            containers: [{
              name: "get-data",
              image: "makotow/wget:dns-fix-0.1.2", <- このように変更します。
              imagePullPolicy: "IfNotPresent",
              command: ["wget",  url, "-P", mountPath, "--no-check-certificate"],
              volumeMounts: [{
                  mountPath: mountPath,
                  name: "pets-data",
              },],
              },],
            volumes: [{
                name: "pets-data",
                persistentVolumeClaim: {
                  claimName: pvc,
                },
            },],
            restartPolicy: "Never",
          },
        },
        backoffLimit: 4,
      },
    };

std.prune(k.core.v1.list.new([
  getDataJob(env.namespace, params.name + "-dataset", params.pvc, params.urlData, params.mountPath),
  getDataJob(env.namespace, params.name + "-annotations", params.pvc, params.urlAnnotations, params.mountPath),
  getDataJob(env.namespace, params.name + "-model", params.pvc, params.urlModel, params.mountPath),
  getDataJob(env.namespace, params.name + "-config", params.pvc, params.urlPipelineConfig, params.mountPath)]))

注釈

なぜ名前解決が失敗しているかについて詳しく知りたい方は以下のGitHub Issues のやりとりが参考になります。

https://github.com/kubernetes/kubernetes/issues/64924

kubernetesクラスタに適応します。

$ ks apply ${ENV} -c get-data-job

INFO Applying jobs kubeflow.get-data-job-dataset
INFO Creating non-existent jobs kubeflow.get-data-job-dataset
INFO Applying jobs kubeflow.get-data-job-annotations
INFO Creating non-existent jobs kubeflow.get-data-job-annotations
INFO Applying jobs kubeflow.get-data-job-model
INFO Creating non-existent jobs kubeflow.get-data-job-model
INFO Applying jobs kubeflow.get-data-job-config
INFO Creating non-existent jobs kubeflow.get-data-job-config

ダウンロード完了しているかを確認します。

「COMPLETIONS」がすべて「1/1」となれば完了です。

$ kubectl get jobs -n kubeflow

NAME                       COMPLETIONS   DURATION   AGE
get-data-job-annotations   1/1           10s        95s
get-data-job-config        1/1           8s         93s
get-data-job-dataset       1/1           74s        96s
get-data-job-model         1/1           20s        95s
ダウンロードしたデータを解凍

ダウンロードしたデータを解凍します。

$ ANNOTATIONS_PATH="${MOUNT_PATH}/annotations.tar.gz"
$ DATASET_PATH="${MOUNT_PATH}/images.tar.gz"
$ PRE_TRAINED_MODEL_PATH="${MOUNT_PATH}/faster_rcnn_resnet101_coco_2018_01_28.tar.gz"

ksonnetにパラメータを指定します。

$ ks param set decompress-data-job mountPath ${MOUNT_PATH}
$ ks param set decompress-data-job pvc ${PVC}
$ ks param set decompress-data-job pathToAnnotations ${ANNOTATIONS_PATH}
$ ks param set decompress-data-job pathToDataset ${DATASET_PATH}
$ ks param set decompress-data-job pathToModel ${PRE_TRAINED_MODEL_PATH}

パラメータの定義を確認します。

$ ks param list decompress-data-job

COMPONENT           PARAM             VALUE
=========           =====             =====
decompress-data-job mountPath         '/pets_data'
decompress-data-job name              'decompress-data-job'
decompress-data-job pathToAnnotations '/pets_data/annotations.tar.gz'
decompress-data-job pathToDataset     '/pets_data/images.tar.gz'
decompress-data-job pathToModel       '/pets_data/faster_rcnn_resnet101_coco_2018_01_28.tar.gz'
decompress-data-job pvc               'pets-pvc'

kubernetesクラスタに適応します。

$ ks apply ${ENV} -c decompress-data-job

INFO Applying jobs kubeflow.decompress-data-job-dataset
INFO Creating non-existent jobs kubeflow.decompress-data-job-dataset
INFO Applying jobs kubeflow.decompress-data-job-annotations
INFO Creating non-existent jobs kubeflow.decompress-data-job-annotations
INFO Applying jobs kubeflow.decompress-data-job-model
INFO Creating non-existent jobs kubeflow.decompress-data-job-model
$ kubectl get job -n kubeflow

NAME                              COMPLETIONS   DURATION   AGE
decompress-data-job-annotations   0/1           25s        25s
decompress-data-job-dataset       0/1           25s        25s
decompress-data-job-model         0/1           24s        24s
get-data-job-annotations          1/1           10s        12m
get-data-job-config               1/1           8s         12m
get-data-job-dataset              1/1           74s        12m
get-data-job-model                1/1           20s        12m

最終的に以下のように decompress-data-job のCOMPLETIONSが「1/1」と表示されれば、解凍完了です。

decompress-data-job-annotations   1/1           3m37s      16m
decompress-data-job-dataset       1/1           108s       16m
decompress-data-job-model         1/1           27s        16m
トレーニングに利用するTensorFlowペットレコードを作成

今回は TensorFlow Detection API を使用します、そこで使えるTFRecordフォーマットに変換する必要があります。

そのための create-pet-record-job を準備しています。このジョブを構成し、適応していきましょう。

変数定義を行います。

$ OBJ_DETECTION_IMAGE="lcastell/pets_object_detection"
$ DATA_DIR_PATH="${MOUNT_PATH}"
$ OUTPUT_DIR_PATH="${MOUNT_PATH}"

ksonnetにパラメータを指定します。

$ ks param set create-pet-record-job image ${OBJ_DETECTION_IMAGE}
$ ks param set create-pet-record-job dataDirPath ${DATA_DIR_PATH}
$ ks param set create-pet-record-job outputDirPath ${OUTPUT_DIR_PATH}
$ ks param set create-pet-record-job mountPath ${MOUNT_PATH}
$ ks param set create-pet-record-job pvc ${PVC}

パラメータの定義を確認します。

$ ks param list create-pet-record-job

COMPONENT             PARAM         VALUE
=========             =====         =====
create-pet-record-job dataDirPath   '/pets_data'
create-pet-record-job image         'lcastell/pets_object_detection'
create-pet-record-job mountPath     '/pets_data'
create-pet-record-job name          'create-pet-record-job'
create-pet-record-job outputDirPath '/pets_data'
create-pet-record-job pvc           'pets-pvc'

kubernetesクラスタに適応します。

$ ks apply ${ENV} -c create-pet-record-job

INFO Applying jobs kubeflow.create-pet-record-job
INFO Creating non-existent jobs kubeflow.create-pet-record-job

稼働状況を確認します。

$ kubectl get jobs -n kubeflow

NAME                              COMPLETIONS   DURATION   AGE
create-pet-record-job             0/1           47s        47s
decompress-data-job-annotations   1/1           3m37s      22m
decompress-data-job-dataset       1/1           108s       22m
decompress-data-job-model         1/1           27s        22m
get-data-job-annotations          1/1           10s        34m
get-data-job-config               1/1           8s         34m
get-data-job-dataset              1/1           74s        34m
get-data-job-model                1/1           20s        34m

COMPLETIONSが「1/1」となれば完了です。

create-pet-record-job   1/1   4m15s   4m15s

ここまででデータの準備ができました。

まとめ

マシンラーニング時に使用するデータをKubernetes上にジョブを投入・実行しダウンロードしました。 ダウンロード先の永続化領域(PVC)はデータを保管刷るタイミングで動的にプロビジョニングされました。 データの保管先を動的につくるために Dynamic Storage Provisioning を行うための Kubernetes のプラグインである Trident を使用しました。

ダウンロード後はトレーニング時に使えるようTFRecordフォーマットに変換しデータ準備を完了としました。 ここで作成した永続化領域、TFRecordは後続のワークフローで引き続き使用していく領域となります。

次からはトレーニングの実施をしていきます。

トレーニング: トレーニングを行い推論モデルを作成する

目的・ゴール: 推論モデルを作成、作成する一般的な手法を体験する

データの準備ができたため実際にトレーニングを実施します。

  1. これまでの構成を使用して分散TensorFlowオブジェクト検出トレーニングジョブを実行
トレーニングを実施

トレーニング用のコンテナイメージを作成します。

作業ディレクトリへ移動します。

$ cd ~/examples/object_detection/docker

ここでは Dockerfile.training を使用します。

TensorFlowのバージョンを参考にしたリポジトリから以下のように変更しています。

$ cat Dockerfile.training
  • 変更前:tensorflow==1.10.0
  • 変更後:tensorflow==1.13.1

最終的なファイルは以下の通りです

# Copyright 2018 Intel Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM ubuntu:16.04

LABEL maintainer="Soila Kavulya <soila.p.kavulya@intel.com>"

# Pick up some TF dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
        build-essential \
        curl \
        libfreetype6-dev \
        libpng12-dev \
        libzmq3-dev \
        pkg-config \
        python \
        python-dev \
        python-pil \
        python-tk \
        python-lxml \
        rsync \
        git \
        software-properties-common \
        unzip \
        wget \
        && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

RUN curl -O https://bootstrap.pypa.io/get-pip.py && \
    python get-pip.py && \
    rm get-pip.py

RUN pip --no-cache-dir install \
        tensorflow==1.13.1

RUN pip --no-cache-dir install \
        Cython \
        contextlib2 \
        jupyter \
        matplotlib

# Setup Universal Object Detection
ENV MODELS_HOME "/models"
RUN git clone https://github.com/NetAppJpTechTeam/models.git $MODELS_HOME

RUN cd $MODELS_HOME/research && \
    wget -O protobuf.zip https://github.com/google/protobuf/releases/download/v3.0.0/protoc-3.0.0-linux-x86_64.zip && \
    unzip protobuf.zip && \
    ./bin/protoc object_detection/protos/*.proto --python_out=.

RUN git clone https://github.com/cocodataset/cocoapi.git && \
    cd cocoapi/PythonAPI && \
    make && \
    cp -r pycocotools $MODELS_HOME/research

ENV PYTHONPATH "$MODELS_HOME/research:$MODELS_HOME/research/slim:$PYTHONPATH"

# TensorBoard
EXPOSE 6006

WORKDIR $MODELS_HOME

# Run training job
ARG pipeline_config_path
ARG train_dir

CMD ["python", "$MODELS_HOME/research/object_detection/legacy/train.py", "--pipeline_config_path=$pipeline_config_path"  "--train_dir=$train_dir"]

編集後、本ハンズオンで使用するコンテナイメージをビルドします。

$ docker build --pull -t pets_object_detection -f ./Dockerfile.training .

コンテナイメージのビルドは割と時間がかかります。 このタイミングで今までの流れで疑問点がないかを確認しましょう。

ビルドが終わったら生成されたイメージの確認をします。

$ docker images

pets_object_detection              latest                         25728e8ade9a        2 minutes ago       2.11GB

docker imageへタグ付けし、コンテナレジストリへpushします。

コンテナレジストへのpush時に認証が求められます。 その際には以下のID、パスワードを入力してください。

  • ユーザ名:userXX
  • パスワード: Netapp1!

XX: ユーザ環境番号

$ docker login https://registry.ndxlab.net
$ docker tag  pets_object_detection  registry.ndxlab.net/user[番号]/pets_object_detection:1.0
$ docker push registry.ndxlab.net/user[番号]/pets_object_detection:1.0
$ cd ~/examples/object_detection/ks-app

トレーニングに関連するパラメータを設定します。

$ PIPELINE_CONFIG_PATH="${MOUNT_PATH}/faster_rcnn_resnet101_pets.config"
$ TRAINING_DIR="${MOUNT_PATH}/train"
$ OBJ_DETECTION_IMAGE="registry.ndxlab.net/user[番号]/pets_object_detection:1.0"

トレーニングに関連するパラメータを設定します。

$ ks param set tf-training-job image ${OBJ_DETECTION_IMAGE}
$ ks param set tf-training-job mountPath ${MOUNT_PATH}
$ ks param set tf-training-job pvc ${PVC}
$ ks param set tf-training-job numPs 1
$ ks param set tf-training-job numWorkers 1
$ ks param set tf-training-job pipelineConfigPath ${PIPELINE_CONFIG_PATH}
$ ks param set tf-training-job trainDir ${TRAINING_DIR}

トレーニングに使用するパラメータを確認します。

$ ks param list tf-training-job

COMPONENT       PARAM              VALUE
=========       =====              =====
tf-training-job image              'registry.ndxlab.net/user[番号]/pets_object_detection:1.0'
tf-training-job mountPath          '/pets_data'
tf-training-job name               'tf-training-job'
tf-training-job numGpu             0
tf-training-job numPs              1
tf-training-job numWorkers         1
tf-training-job pipelineConfigPath '/pets_data/faster_rcnn_resnet101_pets.config'
tf-training-job pvc                'pets-pvc'
tf-training-job trainDir           '/pets_data/train'

Exampleフォルダへ依存ライブラリをコピーします。

$ cp -r ../../../kubeflow_src/kubeflow-deploy/ks_app/vendor/ ./vendor/

続いてTensorFlowのジョブを実行します。 サンプルの内容だと動作しない箇所があるため修正します。

ファイルを編集しv1alpha2からv1beta1ヘ変更しましょう。

$ cd ~/examples/object_detection/ks-app
$ vim components/tf-training-job.jsonnet

編集後に7行目のようになっていれば完了です。

 1 local env = std.extVar("__ksonnet/environments");
 2 local params = std.extVar("__ksonnet/params").components["tf-training-job"];
 3
 4 local k = import "k.libsonnet";
 5
 6 local tfJobCpu = {
 7   apiVersion: "kubeflow.org/v1beta1",
 8   kind: "TFJob",
 9   metadata: {
10     name: params.name,
11     namespace: env.namespace,
12   },

kubernetesクラスタへ反映します。

ks apply ${ENV} -c tf-training-job

ここまででトレーニングを開始することができました。

モニタリングする

トレーニング開始後に稼働状況を確認しましょう。

KubeflowではTensorFlowのジョブをKubernetes上で稼働させるため、 tfjobsというCustomerResouceDefinition(CRD)で定義しています。

ここでは使われているイメージがなにか?中でどのようなものが稼働しているかを確認しましょう。

$ kubectl -n kubeflow describe tfjobs tf-training-job

Name:         tf-training-job
Namespace:    kubeflow
Labels:       app.kubernetes.io/deploy-manager=ksonnet
              ksonnet.io/component=tf-training-job
Annotations:  ksonnet.io/managed:
                {"pristine":"H4sIAAAAAAAA/+xUwW7bMAy97zN4lpP6amCHYUMPA7oFa9EdisKgZcZRLZGCxDQwCv/7IHtriq37g9wIPj4+kXrgC2B095SyE4YGxmNHey+njaRh+1x3pFiDgdFxD...
API Version:  kubeflow.org/v1beta1
Kind:         TFJob
Metadata:
  Creation Timestamp:  2019-03-24T13:40:28Z
  Generation:          1
  Resource Version:    459799
  Self Link:           /apis/kubeflow.org/v1beta1/namespaces/kubeflow/tfjobs/tf-training-job
  UID:                 62d56003-4e3a-11e9-8f7f-42010a9201d1
Spec:
  Clean Pod Policy:  Running
  Tf Replica Specs:
    Master:
      Replicas:        1
      Restart Policy:  Never
      Template:
        Metadata:
          Creation Timestamp:  <nil>
        Spec:
          Containers:
            Args:
              --alsologtostderr
              --pipeline_config_path=/pets_data/faster_rcnn_resnet101_pets.config
              --train_dir=/pets_data/train
            Command:
              python
              research/object_detection/legacy/train.py
            Image:              makotow/pets_object_detection:1.0
            Image Pull Policy:  Always
            Name:               tensorflow
            Ports:
              Container Port:  2222
              Name:            tfjob-port
            Resources:
            Volume Mounts:
              Mount Path:  /pets_data
              Name:        pets-data
            Working Dir:   /models
          Restart Policy:  OnFailure
          Volumes:
            Name:  pets-data
            Persistent Volume Claim:
              Claim Name:  pets-pvc
    PS:
      Replicas:        1
      Restart Policy:  Never
      Template:
        Metadata:
          Creation Timestamp:  <nil>
        Spec:
          Containers:
            Args:
              --alsologtostderr
              --pipeline_config_path=/pets_data/faster_rcnn_resnet101_pets.config
              --train_dir=/pets_data/train
            Command:
              python
              research/object_detection/legacy/train.py
            Image:              makotow/pets_object_detection:1.0
            Image Pull Policy:  Always
            Name:               tensorflow
            Ports:
              Container Port:  2222
              Name:            tfjob-port
            Resources:
            Volume Mounts:
              Mount Path:  /pets_data
              Name:        pets-data
            Working Dir:   /models
          Restart Policy:  OnFailure
          Volumes:
            Name:  pets-data
            Persistent Volume Claim:
              Claim Name:  pets-pvc
    Worker:
      Replicas:        1
      Restart Policy:  Never
      Template:
        Metadata:
          Creation Timestamp:  <nil>
        Spec:
          Containers:
            Args:
              --alsologtostderr
              --pipeline_config_path=/pets_data/faster_rcnn_resnet101_pets.config
              --train_dir=/pets_data/train
            Command:
              python
              research/object_detection/legacy/train.py
            Image:              makotow/pets_object_detection:1.0
            Image Pull Policy:  Always
            Name:               tensorflow
            Ports:
              Container Port:  2222
              Name:            tfjob-port
            Resources:
            Volume Mounts:
              Mount Path:  /pets_data
              Name:        pets-data
            Working Dir:   /models
          Restart Policy:  OnFailure
          Volumes:
            Name:  pets-data
            Persistent Volume Claim:
              Claim Name:  pets-pvc
Status:
  Conditions:
    Last Transition Time:  2019-03-24T13:40:28Z
    Last Update Time:      2019-03-24T13:40:28Z
    Message:               TFJob tf-training-job is created.
    Reason:                TFJobCreated
    Status:                True
    Type:                  Created
    Last Transition Time:  2019-03-24T13:41:20Z
    Last Update Time:      2019-03-24T13:41:20Z
    Message:               TFJob tf-training-job is running.
    Reason:                TFJobRunning
    Status:                True
    Type:                  Running
  Replica Statuses:
    Master:
      Active:  1
    PS:
      Active:  1
    Worker:
      Active:  1
  Start Time:  2019-03-24T13:41:20Z
Events:
  Type     Reason                          Age                    From         Message
  ----     ------                          ----                   ----         -------
  Warning  SettedPodTemplateRestartPolicy  5m18s (x3 over 5m18s)  tf-operator  Restart policy in pod template will be overwritten by restart policy in replica spec
  Normal   SuccessfulCreatePod             5m18s                  tf-operator  Created pod: tf-training-job-ps-0
  Normal   SuccessfulCreateService         5m18s                  tf-operator  Created service: tf-training-job-ps-0
  Normal   SuccessfulCreatePod             5m18s                  tf-operator  Created pod: tf-training-job-worker-0
  Normal   SuccessfulCreateService         5m18s                  tf-operator  Created service: tf-training-job-worker-0
  Normal   SuccessfulCreatePod             5m18s                  tf-operator  Created pod: tf-training-job-master-0
  Normal   SuccessfulCreateService         5m18s                  tf-operator  Created service: tf-training-job-master-0

また、ハンズオン環境に入っているsternというツールを使うことでPodのログを確認することができます。

$ stern tf-training -n kubeflow

ここまででトレーニングの実施が完了です。

今回のサンプルは200000回ステップを実行します。

現在の実行数を確認し、以下の項目を確認してみましょう。

  • 1ステップあたりどれくらいの時間がかかっているか?
  • 200000回実施するまでどれくらいの時間がかかるか?

なぜGPUが必要になるかを実感いただけたのではないでしょうか。CPUだと非常に時間がかかってしまうためGPUが必要になります。 Checkpoint が生成されていることを確認して、一旦TFJobsを削除し作成されたモデルを使いアプリケーションを作成しましょう。

Checkpointのファイル生成状況を確認します。

$ kubectl -n kubeflow exec tf-training-job-master-0 -- ls ${MOUNT_PATH}/train

model.ckpt-X というファイルがあれば完了です。(Xは0以上のものであることを確認ください。)

まとめ

ここでは準備したデータをマシンラーニングを実行し、生成されたモデルを確認刷るところまで実施しました。

  1. トレーニング用のコンテナイメージの作成
  2. 作成したイメージをコンテナレジストリへの登録
  3. Tensorlowをジョブとして実行
  4. チェックポイントの確認

トレーニングが終了したので、次は生成されたモデルをアプリケーションから使用するため、 サーブするというオペレーションを行います。

アプリケーションから使用する: アプリケーションに組み込む

目的・ゴール: アプリケーションから使用する方法を試す
  1. チェックポイントファイルからTensorFlowのグラフを生成
  2. トレーニングしたペット判定機のモデルをTF-Servingを使用してサーブ
アプリケーション適用の準備:グラフの生成

トレーニング中のチェックポイントの番号を確認しましょう。

$ kubectl -n kubeflow exec tf-training-job-master-0 -- ls ${MOUNT_PATH}/train

以下の形式のファイルを参照し変数にセットします。

model.ckpt-[番号] をCHECKPOINT変数にセットします。

ここで一旦TFJobsを削除します。

$ ks delete ${ENV} -c tf-training-job
$ CHECKPOINT="${TRAINING_DIR}/model.ckpt-[番号]"
$ INPUT_TYPE="image_tensor"
$ EXPORT_OUTPUT_DIR="${MOUNT_PATH}/exported_graphs"

ksonnetのパラメータに設定します。

$ ks param set export-tf-graph-job mountPath ${MOUNT_PATH}
$ ks param set export-tf-graph-job pvc ${PVC}
$ ks param set export-tf-graph-job image ${OBJ_DETECTION_IMAGE}
$ ks param set export-tf-graph-job pipelineConfigPath ${PIPELINE_CONFIG_PATH}
$ ks param set export-tf-graph-job trainedCheckpoint ${CHECKPOINT}
$ ks param set export-tf-graph-job outputDir ${EXPORT_OUTPUT_DIR}
$ ks param set export-tf-graph-job inputType ${INPUT_TYPE}

設定したパラメータを確認します。

$ ks param list export-tf-graph-job

COMPONENT           PARAM              VALUE
=========           =====              =====
export-tf-graph-job image              'makotow/pets_object_detection:1.0'
export-tf-graph-job inputType          'image_tensor'
export-tf-graph-job mountPath          '/pets_data'
export-tf-graph-job name               'export-tf-graph-job'
export-tf-graph-job outputDir          '/pets_data/exported_graphs'
export-tf-graph-job pipelineConfigPath '/pets_data/faster_rcnn_resnet101_pets.config'
export-tf-graph-job pvc                'pets-pvc'
export-tf-graph-job trainedCheckpoint  '/pets_data/train/model.ckpt-687'

kubernetesクラスタに適応します。

$ ks apply ${ENV} -c export-tf-graph-job

INFO Applying jobs kubeflow.export-tf-graph-job
INFO Creating non-existent jobs kubeflow.export-tf-graph-job

注釈

TensorFlowにおけるチェックポイントとは、その時点のパラメータやモデルを保管・読み込みができるようにしている機能です。

ジョブが完了したかは以下のコマンドで確認します。

$ kubectl get job -n kubeflow

NAME                              COMPLETIONS   DURATION   AGE
create-pet-record-job             1/1           3m5s       31h
decompress-data-job-annotations   1/1           3m37s      31h
decompress-data-job-dataset       1/1           2m1s       31h
decompress-data-job-model         1/1           24s        31h
export-tf-graph-job               1/1           45s        50m
get-data-job-config               1/1           3s         31h
get-data-job-model                1/1           13s        31h

export-tf-graph-job の Completionが 1/1 になっていれば完了です。

変換が完了したら、モデルが生成されたフォルダをマウントしサーブの準備をします。

アプリケーション適用の準備:モデルのサーブ

ストレージ上の実際のボリュームを確認するため、ストレージへ接続しボリューム名を取得します。

$ cd
$ mkdir models
$ ssh vsadmin@192.168.[ユーザ番号].200 vol show

Password:
Vserver   Volume       Aggregate    State      Type       Size  Available Used%
--------- ------------ ------------ ---------- ---- ---------- ---------- -----
ndxsvm    svm_root     aggr1_01     online     RW          1GB    972.4MB    0%
ndxsvm    trident_kubeflow_pets_pvc_9373b aggr1_01 online RW 20GB 13.96GB   30%
ndxsvm    trident_trident aggr1_01  online     RW          2GB     2.00GB    0%
3 entries were displayed.

上記の例では pets_pvc というキーワードが入っているボリュームをマウントします。 ボリューム名は各自読み替えてください。 Jobが完了すると以下の通りファイルが作成されています。

$ sudo mount -t nfs 192.168.XX.200:/trident_kubeflow_pets_pvc_9373b ./models
$ cd ~/models/exported_graphs
$ ls

checkpoint                  model.ckpt.index  saved_model
frozen_inference_graph.pb   model.ckpt.meta
model.ckpt.data-00000-of-00001      pipeline.config

ここからはアプリケーションへのサーブの準備をします。

$ sudo mkdir saved_model/1
$ sudo cp saved_model/* saved_model/1

ここまででモデルの準備ができました。

モデルをサーブするため変数の定義をします。

上記で定義したモデルのパスを設定します。

今回はバックエンドのストレージはNFSを使用しているため、

MODEL_STORAGE_TYPE はnfsを設定します。

$ MODEL_COMPONENT=pets-model
$ MODEL_PATH=/mnt/exported_graphs/saved_model
$ MODEL_STORAGE_TYPE=nfs
$ NFS_PVC_NAME=pets-pvc

ksonnetに変数を反映します。

$ cd ~/examples/object_detection/ks-app
$ ks param set ${MODEL_COMPONENT} modelPath ${MODEL_PATH}
$ ks param set ${MODEL_COMPONENT} modelStorageType ${MODEL_STORAGE_TYPE}
$ ks param set ${MODEL_COMPONENT} nfsPVC ${NFS_PVC_NAME}
$ ks param set ${MODEL_COMPONENT} defaultCpuImage tensorflow/serving:1.13.0
$ ks param set ${MODEL_COMPONENT} defaultGpuImage tensorflow/serving:1.13.0-gpu

設定した値を確認します。

$ ks param list pets-model

COMPONENT  PARAM            VALUE
=========  =====            =====
pets-model defaultCpuImage  'tensorflow/serving:1.13.0'
pets-model defaultGpuImage  'tensorflow/serving:1.13.0-gpu'
pets-model deployHttpProxy  true
pets-model modelPath        '/mnt/exported_graphs/saved_model'
pets-model modelStorageType 'nfs'
pets-model name             'pets-model'
pets-model nfsPVC           'pets-pvc'

2019/3/28時点ではこのままだとServe時にエラーが出てしまうため、 一部編集します。(TensorFlowのバージョンアップによりコマンドラインが一部変更による影響)

編集対象のファイルは以下のパスに存在するものです。

$ cd ~/examples/object_detection/ks-app/
$ vim vendor/kubeflow/tf-serving[ランダム文字列]/tf-serving.libsonnet

行数としては123行目を一行削除します。内容としては以下の行を削除します。

"/usr/bin/tensorflow_model_server"

モデルをサーブします。

$ ks apply ${ENV} -c pets-model

INFO Applying services kubeflow.pets-model
INFO Creating non-existent services kubeflow.pets-model
INFO Applying deployments kubeflow.pets-model-v1
INFO Creating non-existent deployments kubeflow.pets-model-v1

実行されているかの確認はデプロイメントを確認しましょう。 DESIREDとAVAILABLEが同一の値になっており正常稼働していることが確認できました。

$ kubectl get deploy -n kubeflow pets-model-v1
NAME            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
pets-model-v1   1         1         1            1           12m

ポッドのログを確認してみましょう。

まずはポッド名を確認します。 一番左の文字列がポッド名です。

$ kubectl get pod -n kubeflow | grep pets-model

pets-model-v1-966f4bcd4-x4666                             2/2     Running            0          4m45s

ポッドのログを確認します。1つ前の手順で取得したポッド名を使って確認します。 エラーやワーニングが発生していないことを確認しましょう。

$ kubectl logs pets-model-v1-966f4bcd4-x4666 -n kubeflow -c pets-model

2019-03-26 15:03:22.413505: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:285] SavedModel load for tags { serve }; Status: success. Took 1984623 microseconds.
2019-03-26 15:03:22.414523: I tensorflow_serving/servables/tensorflow/saved_model_warmup.cc:101] No warmup data file found at /mnt/exported_graphs/saved_model/1/assets.extra/tf_serving_warmup_requests
2019-03-26 15:03:22.419865: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: pets-model version: 1}
2019-03-26 15:03:22.423037: I tensorflow_serving/model_servers/server.cc:313] Running gRPC ModelServer at 0.0.0.0:9000 ...
2019-03-26 15:03:22.424251: I tensorflow_serving/model_servers/server.cc:333] Exporting HTTP/REST API at:localhost:8501 ...
[evhttp_server.cc : 237] RAW: Entering the event loop ...

ここまでで生成したモデルをサービングする手順が完了です。

注釈

kubectl logs 上記のコマンドで最後に -c を付与しています。これはPod内に複数のコンテナが起動している場合に特定のコンテナを指定しログを取得しています。 Pod=1つ以上のコンテナの集まりのためこのような構成をとることもできます。

アプリケーション適用:API経由で推論してみる

今回の生成したモデルを使用し推論を実行するためにgRPCクライントを使用することができます。

今回はオペレーション簡易化のため grpc-client-tf-serving というgrpcクライアントを含んだコンテナイメージを作成してあります。

容量が大きいため事前に docker pull しましょう。

$ docker pull registry.ndxlab.net/library/grpc-client-tf-serving:1.0

別コンソールから以下のコマンドを実行しましょう。

Kubernetes外部からモデルサーバにアクセスできるようにポートフォワーディングを設定します。

$ kubectl -n kubeflow port-forward service/pets-model 9000:9000

サンプルフォルダにある画像を推論させます。

$ cd ~/examples/object_detection/serving_script
$ OUT_DIR=.  <= カレントディレクトリとしましたが好きな場所に設定してください。
$ INPUT_IMG="image1.jpg"
$ sudo docker run --network=host \
    -v $(pwd):/examples/object_detection/serving_script --rm -it \
    registry.ndxlab.net/library/grpc-client-tf-serving:1.0 \
    --server=localhost:9000 \
    --model=pets-model \
    --input_image=${INPUT_IMG} \
    --output_directory=${OUT_DIR} \
    --label_map=${TF_MODELS}/models/research/object_detection/data/pet_label_map.pbtxt

実行が完了すると OUT_DIR で指定した箇所に image1-output.jpg というファイル名で保存されています。

ローカル環境へコピーしてどのような画像になっているかを確認しましょう。

まとめ

ここまででAIを創るための一連の流れを体験しました。 実際は非常に泥臭い内容になっていることをご理解いただけたかと思います。

また、ここでは一連のワークフローを体験いただくため画像判定の精度が十分に向上できていない状況です。 画像判定が可能な推論モデルはデータの事前準備の中でダウンロードしていますので、物体が四角で囲われた画像を出力したい場合は次の手順を実施しましょう。

アプリケーションから使用する: トレーニング済みのモデルをアプリケーションに組み込む

目的・ゴール: アプリケーションからトレーニング済みのモデルを使用する方法を試す

ここでは事前のステップで精度が足りなかった推論モデルの替わりにトレーニング済みのモデルをアプリケーションに組み込み画像判定を実施します。 流れとしては以下の通りです。

  1. トレーニング済みのモデルをTFServingを使用してサーブ
  2. 事前のステップと同様にアプリケーションにgrpcクライアントから画像を送り判定を行う
トレーニング済みの推論モデルを使用する

事前にダウンロードした画像判定のモデルをksonnetでmodelPathへ設定します。

$ cd ~/examples/object_detection/ks-app
$ ks param set pets-model modelPath /mnt/faster_rcnn_resnet101_coco_2018_01_28/saved_model

今回使用する TFServing では推論モデルはバージョニングして管理されるため以下のように 1 フォルダを作成し推論モデルを配置します。 1 フォルダにモデルをコピーすることでサーブできるようになります。

$ cd ~/models/faster_rcnn_resnet101_coco_2018_01_28/saved_model
$ sudo mkdir 1
$ sudo cp * 1

準備ができたらモデルのサーブを実行します。

ここまでの手順で、モデルをすでにデプロイしているかたは一旦削除を実施してください。

$ cd ~/examples/object_detection/ks-app
$ ks delete ${ENV} -c pets_model

上記コマンドの実行が成功しても削除・停止処理が実行中の可能性があるため、Podが削除されたことを確認後以下のコマンドを実行してください。

$ ks apply ${ENV} -c pets_model

ここまででトレーニング済みモデルのデプロイが完了です。

ここからは画像認識を再度実施します。

port-forward を実施済みであれば Ctrl-C で停止します、次の手順で再度実行しましょう。

# すでに実行済みであれば
# Ctrl-C で停止後、次のコマンドを実行
$ kubectl -n kubeflow port-forward service/pets-model 9000:9000

サンプルフォルダにある画像を推論させます。

$ cd ~/examples/object_detection/serving_script
$ OUT_DIR=.  <= カレントディレクトリとしましたが好きな場所に設定してください。
$ INPUT_IMG="image1.jpg"
$ sudo docker run --network=host \
    -v $(pwd):/examples/object_detection/serving_script --rm -it \
    registry.ndxlab.net/library/grpc-client-tf-serving:1.0 \
    --server=localhost:9000 \
    --model=pets-model \
    --input_image=${INPUT_IMG} \
    --output_directory=${OUT_DIR} \
    --label_map=${TF_MODELS}/models/research/object_detection/data/pet_label_map.pbtxt

実行が完了すると OUT_DIR で指定した箇所に image1-output.jpg というファイル名で保存されています。 再度画像を確認し画像認識ができていることを確認しましょう。

まとめ

ここではトレーニング済みモデルを適応して再度サーブするということを行いました。

確認いただけたのは一部のパラメータを変更するだけで容易にモデルを変更することができ、 実際に精度が変わるところを体験いただきました。

GPUの活用,異なる環境でのトレーニング、デプロイ

目的・ゴール: GPU・クラウドを活用する・CI/CDパイプラインの作成

この前の章ではデータサイエンスワークフローの一連の流れを体験しました。 基本的なワークフローは今までのとおりですが実施してみて課題がいくつかわかってきました。

例えば、CPUでは処理しきれない計算量をどのように高速化するか? ステージングはオンプレ、本番はクラウドといった複数環境でのユースケースにどう対応するかが挙げられます。

ここからはオプションとして以下のシナリオで対応していきます。

環境へのアクセス方法についてはハンズオンが完了している方へお渡ししますのでお声がけください。

  • フローの中を更に高速化
    • GPUの活用: GPUを活用し演算の高速化を体験
    • KubeflowのコンポーネントであるArogo CIを使い自動化を体験
  • クラウドの活用: メインはオンプレの環境を使用しましたが、これがクラウドに行ってもアーキテクチャの変更なしに同じことができることを確認
フローの中を更に高速化

フローの中の高速化としては2つ題材を挙げています。

1つ目はGPUの活用となります。

2つ目は自動化という観点です、こちらについてはKubeflowのコンポーネントであるArgoCIを使用することで実現できます。

GPUの活用

GPUの活用は容易です。

トレーニング: トレーニングを行い推論モデルを作成する で実施したトレーニングをおこなうところで、GPUの数を指定することで自動でGPUを活用できるようになります。

接続用のコンフィグファイルを配布されたことを確認します。

$ kubectl get node --kubeconfig=config.gpu

Nameの箇所でdgxが表示されていることを確認ください、これがGPUが搭載されたノードになります。

GPUを活用するためのコンテナイメージが必要です。 今回は事前にGPUを利用できるDockerfileを準備していますのでイメージのビルドを実行しましょう。

作業ディレクトリへ移動します。

$ cd ~/examples/object_detection/docker
$ ls Dockerfile.training.gpu

上記Dockerfile.training.gpuが存在することを確認してください。

上位のイメージをビルドします。

$ docker build -t pets_object_detection:1.0-gpu -f Dockerfile.training.gpu .

ビルドが終了したらリポジトリに登録します。

$ docker login https://registry.ndxlab.net
$ docker tag  pets_object_detection:1.0-gpu  registry.ndxlab.net/user[XX]/pets_object_detection:1.0
$ docker push registry.ndxlab.net/user[XX]/pets_object_detection:1.0

ksonnetの環境にGPUクラスタを追加します。

$ cd ~/examples/object_detection/ks-app
$ ks env add gpu --kubeconfig config.gpu

現在のパラメータを確認します。

ここでは numGpu が0であることを確認ください。

$ ks param list tf-training-job

COMPONENT       PARAM              VALUE
=========       =====              =====
tf-training-job image              'registry.ndxlab.net/user[XX]/pets_object_detection:1.0'
tf-training-job mountPath          '/pets_data'
tf-training-job name               'tf-training-job'
tf-training-job numGpu             0
tf-training-job numPs              1
tf-training-job numWorkers         1
tf-training-job pipelineConfigPath '/pets_data/faster_rcnn_resnet101_pets.config'
tf-training-job pvc                'pets-pvc'
tf-training-job trainDir           '/pets_data/train'

GPUを有効にするコンテナイメージの設定とGPU数を設定します。

$ ks param set tf-training-job image 'registry.ndxlab.net/user[XX]/pets_object_detection:1.0-gpu'
$ ks param set tf-training-job numGpu 1

これで tf-train-job を実行するとGPUが使用できるようになります。

tf-train-job を実行については トレーニング: トレーニングを行い推論モデルを作成する を参考に実行ください。

クラウドを活用する

こちらもGPU同様で接続用のコンフィグが配布されたことを確認ください。 以下のようにgkeというキーワードがついているノードが表示されれば切り替え完了です。

$ kubectl get node

NAME                                                STATUS   ROLES    AGE     VERSION
gke-ndxsharedcluster-standardpool01-8b5da289-2pw3   Ready    <none>   4d11h   v1.12.5-gke.5
gke-ndxsharedcluster-standardpool01-8b5da289-ffws   Ready    <none>   4d11h   v1.12.5-gke.5

ここからは最初から手順を実行し、なにも変更することなく実現できることを確認ください。

オペレーションとしては変更はありませんがデータをどこに置くかの検討が必要となってきます。

例えば今回の例だと以下の検討が必要になります。

  • 生成したコンテナイメージの配置場所
  • 別のクラスタで作ったデータを別の環境で持っていく方法

References

コマンドリファレンス

kubectlの使い方・本家へのリンク

公式ガイドへのリンクです。 詳細や使い方等については以下ページを見ることをおすすめします。 このページではよく使うコマンドについてユースケースでまとめました。

デプロイメントの実施

kubectl create/apply/patch/replaceを使用します。

それぞれ便利な点・留意する点があります。

kubectl create デプロイメントの基本系、マニフェストファイルを指定してデプロイし、新規に行う場合に使用します。

$ kubectl create -f deployment.yaml

kubectl applyはcreate/replaceを包含できます。差分反映のアルゴリズムを理解して利用しましょう。 applyの動きとしてはすでにデプロイされていれば更新を行い、デプロイされていなければ新規作成の動きをします。

$ kubectl apply -f deployment.yaml

kubectl replace は稼働中のアプリケーションに対して動的に定義を反映する。

$ kubectl apply -f deployment.yaml

kubectl patch は稼働中のアプリケーションに対して、一部のフィールドを書き換える用途に使用。

状況確認

基本形としては kubectl get オブジェクト名kubectl describe オブジェクト名 になります。 以下は kubectl get ですが、getdescribe に変更することで詳細な情報が確認できるようになります。

よく使うものとしては以下の通りです。

$ kubectl get pod

NAME                               READY     STATUS    RESTARTS   AGE
wordpress-mysql-58cf8dc9f9-t2wnr   1/1       Running   0          2d
$ kubectl get svc

NAME              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
kubernetes        ClusterIP   10.96.0.1    <none>        443/TCP    10d
wordpress-mysql   ClusterIP   None         <none>        3306/TCP   2d
$ kubectl get deployment

NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
wordpress-mysql   1         1         1            1           2d

ネームスペースを指定する場合は -n オプション、または --all-namespaces で全ネームスペースのオブジェクトを確認できます。

$ kubectl get all -n ネームスペース名

マニフェストファイルを使用している場合は get の引数に -f マニフェストファイル を指定すると関連するオブジェクトをすべて表示してくれます。

$ kubectl get -f deployment.yaml

現状のオブジェクトをすべて確認する場合はオブジェクトを指定する箇所に all を設定するとすべてのオブジェクトを確認できます。

$ kubectl get all [-n ネームスペース名]

すべてのネームスペースのすべてのオブジェクトを確認したい場合は以下のとおりです。

$ kubectl get all --all-namespaces

マニフェストファイルを使用したオブジェクトの確認もできます。

-f オプションを使用してデプロイ時に使用したマニフェストファイルを指定すると関連するオブジェクトをすべて表示します。

$ kubectl get -f wordpress-mysql-deploy.yaml
NAME                  TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
svc/wordpress-mysql   ClusterIP   None         <none>        3306/TCP   2d

NAME                 STATUS    VOLUME                         CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc/mysql-pv-claim   Bound     default-mysql-pv-claim-b5e95   20Gi       RWO            ontap-gold     2d

NAME                     DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/wordpress-mysql   1         1         1            1           2d
問題の特定方法について

マニフェストを

kubectl getkubectl describe, kubectl logs を組み合わせて問題箇所を特定していきます。

よく使うコマンド
  • kubectl describe オブジェクト名
  • kubectl describe -f deployment.yaml
トラブルシュートの流れ
  1. 問題箇所の特定

    1. kubectl get -f deployment.yaml で予期しない動作をしている箇所を発見
    2. kubectl describe -f deployment.yaml
  2. うまく行っていない箇所が分かれば該当のPodを確認する

    1. kubectl logs pod ポッド名
    2. 3rd party製の stern というツールもあります。こちらは複数のPodに対して kubectl logs を実行する動きをします。非常に便利なものになります。
  3. 取得できた情報を元に対応実施

    1. マニフェストファイルの修正
オペレーション簡易化のためデフォルトストレージクラスを設定

サンプルで公開されているマニフェストを試したいときに以下の設定をしておくと簡単に起動できるようになります。

  • デフォルトのストレージクラスを設定
  • external ip が付与できるようにするような仕組みを導入する
kubectl patch storageclass [StorageClass名] -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

このドキュメントは整備中です。

用語集

本ラボで出てくる単語集です。

おもにk8s関連の用語になります。 (version1.9時点)

kubernetes、ネットアップの用語集
用語 略称 分類 説明
Deployment deploy kubernetes アプリケーションをデプロイする時に使うもの。デプロイの管理やレプリカの管理を行う。
Service svc kubernetes アプリケーションをkubernetesクラスタ外に公開するときに使用する。
Ingress ing kubernetes アプリケーションをkubernetesクラスタ外に公開するときに使用する。Serviceとの違いはIngressは、Serviceの前段に配置されServiceへのアクセスルールを定義する。
NodePort 特になし kubernetes アプリケーションをkubernetesクラスタ外に公開するときに使用する。各k8sノードでポートを解放しアクセスできるようする。接続したノードにポッドがなければ適切なノードに転送してアクセス可能にする。
LoadBalancer 特になし kubernetes アプリケーションをkubernetesクラスタ外に公開するときに使用する。デプロイ時にロードバランサーサービスに自動登録します。クラウドサービス、オンプレミスでは一部のプラットフォームで対応しています。
ClusterIP 特になし kubernetes アプリケーションをkubernetesクラスタ内に公開するときに使用する。
  • Pod
  • PersistentVolume
  • PersistentVolumeClaim
  • StorageClass
  • Provisioner
  • DevicePlugin
  • ContainerStorageInterface(CSI)
  • ContainerNetworkInterface(CNI)
  • ConfigMap
  • Secret

NetApp用語

  • StorageVirtualMachine(SVM)
  • Logical interface(LIF)
  • vsadmin SVM管理者

References

Kubernets Network (Expose application for external)

Ingressを導入

Level1,2ではデプロイしたアプリケーションが配置されているノードのIPに対してアクセスして稼働を確認していました。 ここからは外部にアプリケーションを公開しアクセスする方法を使用します。

具体的にはServiceを定義する際に指定する「type」が複数提供されています。

  1. ClusterIP
  2. NodePort
  3. LoadBalancer

今回はServiceのtypeをNodePortとして、Serviceの前段にIngressを配置する構成とします。 Ingressを使用してアプリケーションを外部に公開します。 IngressはL7ロードバランサーのような動きをします。

Ingress用のネームスペースを作成

Nginx Ingressをデプロイするネームスペースを作成します。

Nginx Ingressをデプロイするネームスペース用マニフェストファイル
kind: Namespace
apiVersion: v1
metadata:
  name: ingress

以下のコマンドでネームスペースを作成します。

$ kubectl create -f ingress-ns.yaml

  namespace "ingress" created

Nginx Ingressのデプロイメント

helm chartを使ったNginx Ingressのデプロイメントです。

--dry-run を付与してhelmを実行することでドライランモードで実行することが可能です。

$ helm install stable/nginx-ingress --name nginx-ingress --set rbac.create=true --namespace ingress

NAME:   nginx-ingress
LAST DEPLOYED: Mon Apr  9 13:58:29 2018
NAMESPACE: ingress
STATUS: DEPLOYED

RESOURCES:
==> v1/ServiceAccount
NAME           SECRETS  AGE
nginx-ingress  1        0s

==> v1beta1/ClusterRoleBinding
NAME           AGE
nginx-ingress  0s

==> v1beta1/Role
NAME           AGE
nginx-ingress  0s

==> v1beta1/RoleBinding
NAME           AGE
nginx-ingress  0s

==> v1/Service
NAME                           TYPE          CLUSTER-IP     EXTERNAL-IP  PORT(S)                     AGE
nginx-ingress-controller       LoadBalancer  10.96.106.165  <pending>    80:32065/TCP,443:32049/TCP  0s
nginx-ingress-default-backend  ClusterIP     10.101.0.249   <none>       80/TCP                      0s

==> v1beta1/Deployment
NAME                           DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
nginx-ingress-controller       1        1        1           0          0s
nginx-ingress-default-backend  1        1        1           0          0s

==> v1/ConfigMap
NAME                      DATA  AGE
nginx-ingress-controller  1     0s

==> v1beta1/PodDisruptionBudget
NAME                           MIN AVAILABLE  MAX UNAVAILABLE  ALLOWED DISRUPTIONS  AGE
nginx-ingress-controller       1              N/A              0                    0s
nginx-ingress-default-backend  1              N/A              0                    0s

==> v1/Pod(related)
NAME                                           READY  STATUS             RESTARTS  AGE
nginx-ingress-controller-5475585cc9-q5ckc      0/1    ContainerCreating  0         0s
nginx-ingress-default-backend-956f8bbff-5znnc  0/1    ContainerCreating  0         0s

==> v1beta1/ClusterRole
NAME           AGE
nginx-ingress  0s


NOTES:
The nginx-ingress controller has been installed.
It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running 'kubectl --namespace ingress get services -o wide -w nginx-ingress-controller'

An example Ingress that makes use of the controller:

  apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    annotations:
      kubernetes.io/ingress.class: nginx
    name: example
    namespace: foo
  spec:
    rules:
      - host: www.example.com
        http:
          paths:
            - backend:
                serviceName: exampleService
                servicePort: 80
              path: /
    # This section is only required if TLS is to be enabled for the Ingress
    tls:
        - hosts:
            - www.example.com
          secretName: example-tls

If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:

  apiVersion: v1
  kind: Secret
  metadata:
    name: example-tls
    namespace: foo
  data:
    tls.crt: <base64 encoded cert>
    tls.key: <base64 encoded key>
  type: kubernetes.io/tls

Ingressを作成するサンプルです。

L7ロードバランス的なもの
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: mynginx-ingress
spec:
  rules:
    - host: user10.netapp.local
      http:
        paths:
          - backend:
              serviceName: mynginx
              servicePort: 80
            path: /

上記のマニフェストファイルをインプットとして、Ingressを作成します。

$ kubectl create -f ingress-controller.yaml

ingress.extensions "mynginx-ingress" created

$ kubectl get ing

NAME              HOSTS                 ADDRESS   PORTS     AGE
mynginx-ingress   user10.netapp.local             80        51s

Ingressが作成されると、「spec - rules - host」で指定したホスト名でアクセス出来るようになります。 以下の確認では簡易的にcurlコマンドでipとホスト名をマッピングしていますが、通常はDNSへAレコードを登録します。

$ curl -L --resolve user10.netapp.local:80:10.244.0.3 http://user10.netapp.local

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

1つのエンドポイントから複数のサービス呼びだしを実現することができるようになりました。

DNS登録実施

Ingress経由でアクセスするにはホスト名を使用してアクセスします。 本ラボではDNSサーバを共通で準備しています。

Aレコードのエントリは以下のようなスクリプトを準備することで各ユーザが自身で登録することが可能なしくみです。

#!/bin/bash

ETCD="http://192.168.1.1:2379,http://192.168.1.2:2379,http://192.168.1.3:2379"

ETCDCTL_API=3 /usr/local/bin/etcdctl --endpoints $ETCD "$@"

以下のようにしてDNSに登録可能です(jenkins.user1x.ndxlab.net で 192.168.1x.10 を登録します)

$ etcdctl put /dns/net/ndxlab/user1X/jenkins '{"host":"192.168.1x.10"}'

名前解決ができているか確認します。

$ nslookup app-name.user1X.ndxlab.net

インストールするもの・あると便利なもの

Installed apps

kubernetes 関係

  • kubectl
  • helm
  • stern
  • tridentctl (ラボの内部でインストール実施)

DesignDoc

Design: kubernetes クラスタの設計要素について

ラボ環境を作成する際に検討したことやなぜそのようなジャッジをしたかのメモを残す場所

ネットワーク設計

マルチクラスタ構成時のネットワーク構成について マルチテナントはどうする?

ストレージ設計