NetApp Digital Transformation Lab (NDX)¶
はじめに¶
目的¶
- デジタルトランスフォーメーションを支える根幹技術と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の基本操作を確認
流れ¶
- ユーザIDの確認
- 環境へログイン
- 基本コマンド確認、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:確認したポート番号/
アクセス時に以下の画面が表示されれば稼働確認完了です。

状態を確認します。
$ 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 サービス名
Level 1: アプリケーションをコンテナ化する¶
目的・ゴール: アプリケーションをコンテナ化する¶
今回はコンテナに適したアーキテクチャへ変更するまえの段階として、 オンプレミスで仮想マシンで動いているアプリケーションについてコンテナ化をしていきます。
コンテナ技術のDockerを使うことでクラウド、オンプレミス、PCなどどのような環境でもアプリケーションを稼働させることができます。
このレベルではラボで使用するKubernetes上で稼働させるアプリケーションのコンテナイメージを作成しデプロイするのが目標です。
流れ¶
- (Optional) Dockerfileを作成する。
- (Optional) ビルドを行いDockerイメージを作成
- (Optional) 作成したDockerイメージをイメージレジストリに登録
- アプリケーションのマニフェストファイルを作成、イメージレジストリに登録したイメージを使用
- アプリケーションをKubernetes上へデプロイ、稼働確認
コンテナ化の準備¶
本ラボでは以下のミドルウェアやスタックを使ったアプリケーションを想定しています。 基本的にはアプリケーションをコンテナ化する際にはDockerHubで作成済みのイメージを使用することで効率よくコンテナ化することができます。
Web/AP レイヤー
- nginx
- apache
- tomcat
Databaseレイヤー
- mySQL
- Postgress
- Oracle
- MongoDB
コンテナイメージの作成¶
このステップはアプリケーションを持ち込みの場合や複雑なスタックをコンテナ化する際に行うステップです。選択したアプリケーションによっては不要なステップになるのでやるべきかどうかを確認してください。
その場合、 アプリケーションのマニフェストファイルを作成してデプロイ からはじめてください。
想定するアプリケーションのコンテナイメージを作成します。
Dockerfile のリファレンス Dockerfile Reference ファイル
留意点としては以下の通りです。
アプリケーションの配置をDockerfile内に配置
基本となるコンテナイメージについてはDockerHubで探してベースイメージとする
静的な構成となっていないか(IPパスワードのべた書きなど)
- 環境変数で設定出来るよう設計する。のちほどk8sのSecretなどでパスワードを保存
冪等性はコンテナイメージ側で対応する。責任範囲を明確にしてイメージを作成
ステートフルなものについてはコンテナに適したものにする
- データ永続化については Level 2: ステートフルコンテナの実現 にて実施
ヒント
記述例を提示します。このままビルドしてもイメージは作成されませんのであくまで記述例としてみてください。 どうしても進まない場合は サンプル: 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にアクセスするとログイン画面が開きます。

ログイン・パスワードは以下のものを利用ください。
- user: user[ユーザ環境番号]
- pass: Netapp1!
ログインするとプロジェクト一覧が表示されます。

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

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つを実施します。
どの部分を実施しているかを把握しながらすすめましょう。
- MySQL 用のSecretオブジェクトを作成
- MySQL をデプロイ
- 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を起動します。
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をデプロイします。
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が接続先を見つけるために使っている例が上記のマニフェストとなります。
- 参考URL: k8s label
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についても確認しましょう。
- 参考URL: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
注釈
kubectl引数の省略系について
今回はServiceの確認をする際に svc
という省略形でコマンドを実行しました。
他のオブジェクトも同様に省略形があります。コマンド入力を省力化したい場合は省略形も使ってみましょう。
kubectl --help
や kubectl XX --help
コマンドで確認できます。
まとめ¶
kubectlやYAMLで記載するマニフェストファイルを使ってk8sへのデプロイが体感できたかと思います。 実運用になるとこのYAMLをたくさん書くことは負荷になることもあるかもしれません.
その解決のためにパッケージマネージャーHelm 等を使ってデプロイすることが多いかと思います。 このラボでは仕組みを理解していただき、応用出来ることを目的としています。
ここまでで Level1 は終了です。
Level 2: ステートフルコンテナの実現¶
目的・ゴール: アプリケーションのデータ永続化を実現¶
アプリケーションは永続化領域がないとデータの保存ができません。 KubernetesではStatic provisioningとDynamic provisioningの2つの永続化の手法があります。
このレベルではDynamic provisioningを実現するためDynamic provisionerであるTridentをインストールし、 マニフェストファイルを作成しデータの永続化をすることが目標です。
流れ¶
Dynamic storage provisioningを実現(Tridentのインストール)
StorageClassの作成
PVCをkubernetesマニフェストファイルに追加
- 作成したStorageClassを使用する
- PVCをkubernetesにリクエストした時点で動的にストレージがプロビジョニングされる
アプリケーションを稼働させて永続化ができていることを確認
コンテナでの永続データのカテゴライズ¶
コンテナ化されたアプリケーション、環境での永続データは 以下のように分類して考え必要な物をリストアップしました。
- データベースのデータファイル、ログファイル
- 各サーバのログファイル
- 設定ファイル
- 共有ファイル
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をコピーして使います。
パラメータ名 | 説明 | 設定内容 |
---|---|---|
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 内に残るオブジェクトが完全になくなった事を確認する事をおすすめします。
Tridentインストールのバージョン間の差異¶
インストール時の動作が19.0xで何度か変化しているので整理しておきます。
Version | インストール時backend.json | 初回バックエンド登録 |
〜19.01 | 必要 | されない |
19.04 | 必要 | される |
19.07〜 | 不要 | されない |
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を作成するときに以下の属性を設定できます。 これらの属性のパラメータを組み合わせてストレージサービスをデザインします。
設定可能な属性 | 例 |
---|---|
性能に関する属性 | メデイアタイプ(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作成方法のサンプルです。
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の項目を追加し、ダイナミックプロビジョニングを使用しデータを永続化出来るアプリケーションを定義します。
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つを実施してください。 このセクションはどうやって実現するかを考えていただくためあえて答えは書いてありません。
- 上記PVCマニフェストファイルを作成し、PVCオブジェクトを作成
- 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つの視点から確認したいと思います。
- アプリケーションであれば再度ログインして保存したデータを確認します。
- バックエンドストレージに動的にボリュームが作成されていることを確認します。
$ 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にデータソースの名前を指定します。
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名を指定します。
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例と捉えてください。

登場しているツールの以下のように分類でき、それぞれ代表的なものをキーワードとして上げます。
- 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がデプロイできたら実際にアプリケーションの変更を行い自動でデプロイするところまでを目指します。
流れ¶
- Jenkins をインストールする
- Jenkins 内部でジョブを定義する。
- あるアクションをトリガーにビルド、テストを自動実行する。
- 自動で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つ存在します。
- Helm Chartでデプロイする方法 (手軽にインストールしたい人向け)
- Level1,2と同じようにyamlファイルを作成し、デプロイする方法(仕組みをより深く知りたい人向け)
- 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チャートのインストール・Jenkinsのカスタマイズ¶
今回はJenkinsを導入するにあたり環境に併せてカスタマイズを行います。 Helmは以下のURLに様々なものが公開されています。パラメータを与えることである程度カスタマイズし使用することができます。 Helm chartと同等のディレクトリにvalues.yamlというファイルが存在し、これを環境に併せて変更することでカスタマイズしデプロイできます。
今回のJenkinsのデプロイでは2つの公開方法が選択できます。
1つ目が、今回の環境では Service
の type
を LoadBalancer
としてしてデプロイすると 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ファイルに追記済みです。
# 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ログイン後、クレデンシャルを事前に作成します。

jenkins 導入済みのネームスペースにサービスアカウントを作成します。
kubectl create clusterrolebinding jenkins --clusterrole cluster-admin --serviceaccount=jenkins:default
Configurationから「Kubernetes設定」を選択します。

ここでは必要となるパラメータを設定していきます。
- 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機能が活用できます。
- https://jenkins.io/doc/book/pipeline/
- https://github.com/jenkinsci/kubernetes-plugin/blob/master/README.md
ここではテンプレートを準備しました、上記の様なパイプラインを実装してみましょう。 Jenkins ではパイプラインを構築するために2つの記述方法があります。
- Declarative pipeline syntax https://jenkins.io/doc/book/pipeline/#declarative-pipeline-fundamentals
- Scripted pipeline syntax https://jenkins.io/doc/book/pipeline/#scripted-pipeline-fundamentals
それぞれの違いついてはこちら。
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'
}
}
}
}
}
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を作成します。

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化やバックアップをどうするかを検討します。
流れ¶
本レベルでは運用時に課題となり、解決が必要となる項目についてリストしました。 現在の環境に対して変更をして見てください。ここでは実際に手を動かしてもらってもいいですし、 チーム内でディスカッションの材料にしてください。
アプリケーションの可用性を向上させる¶
すでに Deployment
で使われているかもしれませんが、replica数などを定義できます。一般的なアプリケーションデプロイに使用します。
各ノードでコンテナを稼働させる DaemonSet
があります。ログ収集用のデーモンやメトリクス収集などのユースケースがあります。
レプリカ生成時の順序制御、各ポッドにPVを割り当てることができる StatefulSet
があります。主にクラスタ、分散環境におけるユースケースで使用するものです。
kubernetes上のオブジェクト名は以下の通りです。
- ReplicaSet
- DaemonSet
- StatefulSet
Deployment
の Pod 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はブラウザーのUIを通したグラフィカルなインターフェースを持っており、様々な環境のkubernetesクラスタを管理するとともに、コンテナーの管理、アプリケーションの管理も行うことができます。ここでいうアプリケーションは kubernetes 上で動くものすべてです。
Rancherの機能については、以下のサイトからご確認ください。
Your Enterprise Kubernetes Platform | Rancher Labs https://rancher.com/
ここではRancherの導入から、アプリケーションのデプロイを実施します。 アプリケーションとして、kubernetes クラスタを監視するソフトウェアスタック(Prometheus+Grafana)をkubernetes上で簡単に起動してみます。
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をインストールします。
以下のDockerHubのタグでv2.x系の最新のバージョンを確認してください。。
今回は、v2.2.6 をインストールします。
docker run -d --restart=unless-stopped \
-p 80:80 -p 443:443 \
rancher/rancher:v2.2.6
上記のRancherをインストールしたホストのIPアドレスでブラウザーを開くと以下のような画面が表示されます。

パスワードを指定するか、ランダムのパスワードを生成して Continue を押します。
次に、作っておいた Kubernetesクラスターを Rancherから認識できるようにインポートします。 Globalから Add Cluster ボタンを押します。

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

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

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

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ダッシュボードにインポートされたクラスターが表示されます。

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

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

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

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

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

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

Prometheusをクリックします。

上記の Workloads
を確認します。

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

画面が表示されれば正常にデプロイされています。
インフラの可用性を向上させる¶
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をデプロイし監視することにチャレンジしてみましょう。
Prometheusは長期保管はできないため長期保管の際は別途保管が必要です。
大きく以下のバックアップ対象が存在します。
- 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を別ノードで起動、対象ノードをアップデートし、ポッドを戻す。
アプリケーションのポータビリティはコンテナで実現。 別クラスタで作成されたPVはそのままは参照できないので以下の方法を検討する。
- CSI (Container Storage Interface)の既存ボリュームのインポートに対応をまつ
- Heptio ark: https://github.com/heptio/ark + SVM-DR
Dynamic ProvisioningされたPVCのPod障害時の動作については以下のような動作になります。 PVCはTridentを使ってデプロイしたものです。
- 停止したPodは別ノードで立ち上がる
- Podからマウントしていたボリュームは再度別ノードでもマウントされデータの読み書きは継続可能
Stateful Set
を使い、MongoDBを複数ノードで構成し上記の検証を行った結果が以下のリンク先で確認できます。
Level 5: Microservice化¶
目的・ゴール: Microserviceを支えるインフラの技術・テクノロジースタックを活用¶
Level4までで既存のアプリケーションをコンテナ化して本番運用可能かつ迅速にリリースできる仕組みを作りました。 このレベルではアプリケーションをコンポーネント単位に分割していった際に必要となるインフラの仕組みを適用します。
また、Microserviceを実現するServiceMesh、”Istio”を体験します。
流れ¶
- Microservice化していくにあたって発生する課題
- 解決する技術の1つ "Istio"
Microservice化していくにあたって発生する課題¶
マイクロサービス化が進むと1つのシステムを複数の細かいコンポーネントにわけ、独立したサービスとして迅速にデプロイできるようになり、
その反面、モノリスなアプリケーションでは問題とならなかったサービス間の接続、モニタリング、通信の制御、エンドツーエンドの認証などが課題として顕在化してきます。
マイクロサービスを実現するためには上記の課題を解決する必要があります。
解決する技術の1つ "Istio"¶
ここでは上記の課題を解決するための"Istio"について紹介します。
モノリスなアプリケーションから分散型・コンポーネントに分割されたアプリケーションへコンポーネント分割際に発生する課題に対して有効です。
最初はモノリスなアプリケーションをそのままコンテナ化し徐々にコンポーネントに分けていく、そしてサービス間通信をうまくやるためのServiceMeshとして開発されました。
ポイントはアプリケーションに影響なくIstioを導入できることです。
ここでは簡単にIstioを体験してみます。
マイクロサービスの参考アーキテクチャ¶
Microserviceのアプリケーションを様々なテクノロジー、言語を使って実現しているサンプルがあります。 このサンプルでは様々なスタックで必要とされるテクノロジーが網羅されているため参考になります。
コンピューティングの最適化¶
サーバレスアーキテクチャというキーワードが注目されています。
AWSであればLambda, AzureであればDurableFunction, GCPであれば Cloud Functions といった各クラウドプロバイダーがそれぞれサービスを提供しています。
もちろんKubernetes上でも同様のサーバレスアーキテクチャを実現するものがあります。
Knative というものが存在しており、Helm、Istioで構成されておりセットアップは最小でできるようになっています。
さらなる進化¶
マルチクラウド、ハイブリッドクラウドは一部実践編で体験することができます。
マルチクラウド化にはIstioのCrossClusterMesh等を使った方法でも実現が可能です。
全般的にネットワークの設計をどうするかという課題が残ります。
- マルチクラウド化
- ハイブリッドクラウド化
実践編: はじめに¶
概要¶
Kubernetes上でAIアプリケーションを作成するハンズオンです。 まずは一連の流れを体験することが目的です。
ハンズオンの流れ¶
- 当日の環境確認
- ハンズオンを行うための環境構築
- トレーニングデータの準備
- サンプルの学習コードを使ってモデル作成
- アプリケーションから利用するためのモデルを生成
- 生成したモデルを生成し、クライアントから呼び出し、推論を行う
ゴール・目的¶
基本コース¶
Kubernetes環境下でAIアプリケーションのデプロイまでの1連の流れを体験する。
- データの取得から、データ準備、トレーニング、サーブまでを体験
最終的なアウトプットとしては以下のように、物体に写真の中の物体にマーキングされた画像を出力するのがゴールです。

オプション¶
環境・リソースに限りがあるため終わった方で試したい方はお伝えください。
- オプションでフローの中を更に高速化
- 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を作成しデータをダウンロード解凍を実施します。
概要¶
ここでは以下のフローで進んでいきます。 一覧としては以下の通りです。一つ一つの用語がわからない場合があるかもしれませんがまずは動かし体験することを目標としています。
- テスト、トレーニングデータ、トレーニング結果(モデル)を保管するPersistentVolumeClaimをデプロイする
- データセットのダウンロード、データセットのアノテーション、事前トレーニング済みモデルチェックポイント、トレーニングパイプラインの構成ファイルをダウンロード
- ダウンロードしたデータ・セット、事前トレーニング済みデータ・セット、データ・セットアノテーションの解凍
- ペット検知器モデルをトレーニングするので、TensorFlowペットレコードを作成
ハンズオンではksonnetというツールを使用し進めます。
この例で使用する一連のコンポーネントを含む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 のやりとりが参考になります。
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は後続のワークフローで引き続き使用していく領域となります。
次からはトレーニングの実施をしていきます。
トレーニング: トレーニングを行い推論モデルを作成する¶
目的・ゴール: 推論モデルを作成、作成する一般的な手法を体験する¶
データの準備ができたため実際にトレーニングを実施します。
- これまでの構成を使用して分散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以上のものであることを確認ください。)
まとめ¶
ここでは準備したデータをマシンラーニングを実行し、生成されたモデルを確認刷るところまで実施しました。
- トレーニング用のコンテナイメージの作成
- 作成したイメージをコンテナレジストリへの登録
- Tensorlowをジョブとして実行
- チェックポイントの確認
トレーニングが終了したので、次は生成されたモデルをアプリケーションから使用するため、 サーブするというオペレーションを行います。
アプリケーションから使用する: アプリケーションに組み込む¶
目的・ゴール: アプリケーションから使用する方法を試す¶
- チェックポイントファイルからTensorFlowのグラフを生成
- トレーニングしたペット判定機のモデルを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を創るための一連の流れを体験しました。 実際は非常に泥臭い内容になっていることをご理解いただけたかと思います。
また、ここでは一連のワークフローを体験いただくため画像判定の精度が十分に向上できていない状況です。 画像判定が可能な推論モデルはデータの事前準備の中でダウンロードしていますので、物体が四角で囲われた画像を出力したい場合は次の手順を実施しましょう。
アプリケーションから使用する: トレーニング済みのモデルをアプリケーションに組み込む¶
目的・ゴール: アプリケーションからトレーニング済みのモデルを使用する方法を試す¶
ここでは事前のステップで精度が足りなかった推論モデルの替わりにトレーニング済みのモデルをアプリケーションに組み込み画像判定を実施します。 流れとしては以下の通りです。
- トレーニング済みのモデルをTFServingを使用してサーブ
- 事前のステップと同様にアプリケーションに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の使い方・本家へのリンク¶
公式ガイドへのリンクです。 詳細や使い方等については以下ページを見ることをおすすめします。 このページではよく使うコマンドについてユースケースでまとめました。
- https://kubernetes.io/docs/reference/kubectl/overview/
- https://kubernetes.io/docs/reference/kubectl/cheatsheet/
- https://kubernetes.io/docs/tasks/debug-application-cluster/debug-application-introspection/
- https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/
- https://kubernetes.io/docs/reference/kubectl/cheatsheet/
デプロイメントの実施¶
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
ですが、get
を describe
に変更することで詳細な情報が確認できるようになります。
よく使うものとしては以下の通りです。
$ 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 get
と kubectl describe
, kubectl logs
を組み合わせて問題箇所を特定していきます。
よく使うコマンド¶
- kubectl describe オブジェクト名
- kubectl describe -f deployment.yaml
トラブルシュートの流れ¶
問題箇所の特定
kubectl get -f deployment.yaml
で予期しない動作をしている箇所を発見- kubectl describe -f deployment.yaml
うまく行っていない箇所が分かれば該当のPodを確認する
- kubectl logs pod ポッド名
- 3rd party製の
stern
というツールもあります。こちらは複数のPodに対してkubectl logs
を実行する動きをします。非常に便利なものになります。
取得できた情報を元に対応実施
- マニフェストファイルの修正
オペレーション簡易化のためデフォルトストレージクラスを設定¶
サンプルで公開されているマニフェストを試したいときに以下の設定をしておくと簡単に起動できるようになります。
- デフォルトのストレージクラスを設定
- external ip が付与できるようにするような仕組みを導入する
kubectl patch storageclass [StorageClass名] -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
このドキュメントは整備中です。
用語集¶
本ラボで出てくる単語集です。
おもにk8s関連の用語になります。 (version1.9時点)
用語 | 略称 | 分類 | 説明 |
---|---|---|---|
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¶
サンプルアプリケーション¶
- Mocアプリ(Rails) https://github.com/renjikari/moc_app
- https://qiita.com/renjikari/items/d502730b687c19993579
- https://github.com/SecureSkyTechnology/BadLibrary/tree/master/src
- OracleRAC https://github.com/Seth-Miller/12c-rac-docker
- kubernetes examples https://github.com/kubernetes/examples
- Microservices デモ集 https://github.com/microservices-demo/microservices-demo
- Postgress https://github.com/paunin/PostDock
- JHipSter https://www.jhipster.tech/
Kubernetes¶
Kubernetes Monitoring¶
Kubernetes configuration¶
Test tools¶
CI/CD for kubernetes applications¶
- JenkinsX http://jenkins-x.io/
- Skaffolld https://github.com/GoogleCloudPlatform/skaffold
- Jenkins on GCP https://cloud.google.com/solutions/jenkins-on-kubernetes-engine-tutorial
- Pipeline https://cloud.google.com/solutions/continuous-delivery-jenkins-kubernetes-engine
- GitOps https://www.weave.works/blog/gitops-high-velocity-cicd-for-kubernetes
Kubernets Network (Expose application for external)¶
- Ingress https://github.com/nginxinc/kubernetes-ingress/tree/master/examples
- MetalLB https://metallb.universe.tf/
- Trafik
- istio https://istio.io/
Kubernetes Security¶
NetApp demo¶
コンテナデザインパターン¶
コンテナストレージの選択¶
Ingressを導入¶
Level1,2ではデプロイしたアプリケーションが配置されているノードのIPに対してアクセスして稼働を確認していました。 ここからは外部にアプリケーションを公開しアクセスする方法を使用します。
具体的にはServiceを定義する際に指定する「type」が複数提供されています。
- ClusterIP
- NodePort
- LoadBalancer
- https://medium.com/@maniankara/kubernetes-tcp-load-balancer-service-on-premise-non-cloud-f85c9fd8f43c
- https://kubernetes.io/docs/concepts/services-networking/service/
今回はServiceのtypeをNodePortとして、Serviceの前段にIngressを配置する構成とします。 Ingressを使用してアプリケーションを外部に公開します。 IngressはL7ロードバランサーのような動きをします。
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を作成するサンプルです。
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
DesignDoc¶
Design: kubernetes クラスタの設計要素について¶
ラボ環境を作成する際に検討したことやなぜそのようなジャッジをしたかのメモを残す場所
ネットワーク設計¶
マルチクラスタ構成時のネットワーク構成について マルチテナントはどうする?
ストレージ設計¶
Trident Design Guide: https://netapp-trident.readthedocs.io/en/stable-v19.01/dag/kubernetes/storage_configuration_trident.html?highlight=vserver%20create#storage-configuration-for-trident
管理ポートのネットワークはどうするか?
アーキテクチャとして、cluster管理 LIF を公開するか?それともSVM管理LIFか?
- マルチテナントを構成するのであれば k8s クラスタ単位にSVMを割り当てるデザインとする。マネジメントもSVMをユーザに渡す。
StorageClass までを管理者側で作成
PersistentVolumeClaim は開発者が作成するが、ある程度のパターンはカタログ化して提供する。
無制限に作られてしまうと困るので、k8s 側で Storage Quota を設定、Namespace 毎に指定。ストレージ側では設定なし。 https://kubernetes.io/docs/concepts/policy/resource-quotas/