仮想化通信

日本仮想化技術株式会社の公式エンジニアブログ

Jujuで構築するKubernetesでGPUを使うには(2)手書き文字解析編

tech.virtualtech.jp

今回は前回構築したKubernetes GPU環境でMNISTを動かしてみます。 MNISTは手書き文字を解析するサンプルプログラムです。

これを実現する方法はいくつかありますが、前回予告した通り、Chainerを使って実行してみます。

Podを作成するにあたって、Docker Hubに上げられたChainerイメージを使います。

Python2ベースのイメージとして「chainer/chainer:latest-python2」、Python3ベースのイメージとして「chainer/chainer:latest-python3」が存在します。今回はPython3ベースのイメージを使いますが、必要に応じて好きな方を設定してください。

YAMLは次のように記述したものを使います。

apiVersion: v1
kind: Pod
metadata:
  name: chainer
spec:
  restartPolicy: OnFailure
  containers:
    - name: chainer
      image: chainer/chainer:latest-python3
      tty: true

      resources:
        limits:
          nvidia.com/gpu: 1

次のように実行して、Podを作成します。

$ kubectl create -f chainer-gpupod.yml
pod "chainer" created
(Podを作成)

$ kubectl get -f chainer-gpupod.yml
NAME      READY     STATUS              RESTARTS   AGE
chainer   0/1       ContainerCreating   0          5s

$ kubectl get -w -f chainer-gpupod.yml
NAME      READY     STATUS              RESTARTS   AGE
chainer   0/1       ContainerCreating   0          11s
chainer   1/1       Running   0         25s
^C
(ステータスを確認)

シェルにログインしてみます。後で必要なので、wgetパッケージをインストールしておきます。

$ kubectl exec -it chainer bash
root@chainer:/#
(シェルにログインできた)

root@chainer:/# apt update && apt install wget
(wgetパッケージのインストール)

Pythonモジュールがインストールされていることを確認しましょう。Python 2版の場合はpip2、Python 3版の場合はpip3コマンドでlistを出力してみます。

root@chainer:~# pip3 list
chainer (5.0.0a1)
cupy-cuda80 (5.0.0a1)
fastrlock (0.3)
filelock (3.0.4)
numpy (1.14.3)
pip (8.1.1)
protobuf (3.5.2.post1)
setuptools (20.7.0)
six (1.11.0)
wheel (0.29.0)

念のため、現在インストールされているバージョンと同じソースコードをダウンロードしてみます。

root@chainer:~# wget https://github.com/chainer/chainer/archive/v5.0.0a1.tar.gz
root@chainer:~# tar zvxf v5.0.0a1.tar.gz

まずは次のように実行して、CPUモードで画像解析を実行してみます。

root@chainer:~# python3 chainer-5.0.0a1/examples/mnist/train_mnist.py
GPU: -1
# unit: 1000
# Minibatch-size: 100
# epoch: 20

Downloading from http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz...
/usr/local/lib/python3.5/dist-packages/chainer/training/extensions/plot_report.py:25: UserWarning: matplotlib is not installed on your environment, so nothing will be plotted at this time. Please install matplotlib to plot figures.

  $ pip install matplotlib

  warnings.warn('matplotlib is not installed on your environment, '
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
     total [##................................................]  4.17%
this epoch [#########################################.........] 83.33%
       500 iter, 0 epoch / 20 epochs
    40.105 iters/sec. Estimated time to finish: 0:05:07.333278.
...
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.192088    0.104656              0.942283       0.9655                    15.9014
2           0.0761355   0.083492              0.976633       0.9748                    34.297
3           0.0481078   0.0801713             0.984583       0.9774                    53.7614
4           0.0363545   0.0682803             0.988733       0.9803                    73.6677
5           0.0281913   0.0679281             0.990517       0.9819                    95.086
6           0.0233321   0.0992965             0.99245        0.9767                    116.134
7           0.0247206   0.0955383             0.992033       0.9757                    136.834
8           0.0157767   0.0864075             0.99495        0.9811                    158.071
9           0.0165213   0.0684731             0.995          0.983                     179.839
10          0.0147778   0.0693633             0.994833       0.9853                    202.334
11          0.0119251   0.0902634             0.995933       0.9821                    225.362
12          0.0139303   0.0877024             0.995683       0.9818                    248.44
13          0.0111728   0.0823854             0.996633       0.9838                    272.442
14          0.0146516   0.0914885             0.995267       0.9821                    297
15          0.00734373  0.0916999             0.997867       0.9829                    322.92
16          0.00987204  0.116907              0.997133       0.9797                    351.075
17          0.0141462   0.117645              0.995867       0.9775                    377.785
18          0.0121944   0.0804133             0.996233       0.985                     405.164
19          0.00477812  0.0914235             0.99845        0.9846                    433.902
20          0.0133259   0.123587              0.9963         0.9789                    463.388

CPUモードでは解析に要する時間は当然ながら、CPUの性能に依存します。5分程度かかりそうと予測されています。 では次に、GPUを使って解析をしてみます。

root@chainer:~# python3 chainer-5.0.0a1/examples/mnist/train_mnist.py -g0
...
Estimated time to finish: 0:00:56.929975.
...
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.187311    0.0982425             0.943567       0.9681                    13.1751
2           0.0729037   0.0750831             0.977232       0.9768                    16.2432
3           0.0483815   0.0741541             0.984315       0.9775                    19.2818
4           0.0348181   0.0726876             0.988632       0.9792                    22.3202
5           0.0310322   0.0824915             0.989898       0.9777                    25.5088
6           0.0235344   0.0737441             0.991981       0.9814                    28.4955
7           0.0192432   0.0733679             0.993332       0.9824                    31.5288
8           0.0189031   0.0744087             0.993933       0.9831                    34.5434
9           0.016433    0.0905501             0.994599       0.9788                    37.5528
10          0.0141957   0.0850642             0.995566       0.9817                    40.6166
11          0.0151095   0.0771753             0.995232       0.9826                    43.6603
12          0.0112923   0.0848214             0.996215       0.9812                    46.7101
13          0.0148123   0.0836868             0.995183       0.9825                    49.7578
14          0.00946035  0.0908514             0.996815       0.9837                    52.992
15          0.0118137   0.103228              0.996298       0.9798                    56.034
16          0.0105525   0.100688              0.996932       0.9814                    59.044
17          0.00669109  0.105021              0.998333       0.9819                    62.0895
18          0.0142701   0.0929249             0.996015       0.9833                    65.0944
19          0.00628928  0.0943469             0.997999       0.9837                    68.124
20          0.00698401  0.0944361             0.997782       0.9826                    71.1269

手元の環境では1分以下、つまりCPUで行ったときの1/5の時間で解析できることがわかります。 GPUを使う事でデータ解析を短縮できることが確認できました。

Jujuを使えばKubernetesの環境の構築や、KubernetesでGPUを使う構成も簡単です。 ぜひお試しください。また、お手伝いできる事があればお問い合わせください。

KubernetesクラスタへSpinnakerを構築

Kubernetesはコンテナアプリケーションのオーケストレーターです。

yamlによるサービス構築がkubectlコマンドで実行できますが、 さらに自動化されたデプロイ環境が欲しくなってくるのではないでしょうか。

そこで前回構築したKubernetesにContinuous Delivery Platformである 「Spinnaker」を動作させ、パイプラインによる自動デプロイを行いました。

筆者: コムシス情報システム株式会社 山本 聡(やまもと さとる)

Kubernetesクラスタ環境の用意

まずはKubernetesクラスタを用意します。

以前当ブログにおいてJUJU+MAASでの環境構築を行っていますので、以下のエントリーを参考ください。

http://tech.virtualtech.jp/entry/2018/03/29/104216

Kubernetes永続ボリューム設定

SpinnakerをKubernetes上にデプロイするにあたって、ポッドが永続ボリューム要求を出します。

これを処理するため、ブロックストレージ等によるバックエンドの構築を行い、Kubernetesにストレージクラスを設定します。

永続ボリューム用のストレージ選定

Kubernetesの永続ボリュームに使用できるストレージはいくつかありますが、今回はCephを選びました。

中でもKubernetes対応のCephストレージソフトウェアである「ROOK」を使用します。

https://rook.io

Kubernetesの特権モードを有効化

ROOKを構築するにはKubernetesの特権モードを有効化しておく必要があります。

JUJUを使用している場合、MasterとWorkerのconfigureからallo-privileged=trueに設定します。

f:id:virtualtech:20180523133945p:plain

ROOKのダウンロード

ROOKをGithubからクローンでダウンロードします。

$ git clone https://github.com/rook/rook.git
$ cd rook/cluster/examples/kubernetes/

ROOK Operatorのデプロイ

始めにROOK Operatorをデプロイします。

kubectl create -f rook-operator.yaml

正常にデプロイできたかをポッドの一覧から確認します。

$ kubectl -n rook-system get pod
NAME                             READY     STATUS    RESTARTS   AGE
rook-agent-4ljgl                 1/1       Running   0          1m
rook-agent-988kd                 1/1       Running   0          1m
rook-operator-7cdfdb4bdc-ksg4b   1/1       Running   0          1m

ROOK Clusterのデプロイ

次にROOK Clusterをデプロイします。

kubectl create -f rook-cluster.yaml

こちらも正常にデプロイできたかをポッドの一覧から確認します。 ceph-osdやceph-monがworker数分作成されています。

$ kubectl -n rook get pod
NAME                              READY     STATUS    RESTARTS   AGE
rook-api-7f9df6fdfd-ph96w         1/1       Running   0          1m
rook-ceph-mgr0-67df677685-mzxvd   1/1       Running   0          1m
rook-ceph-mon0-5c66f              1/1       Running   0          1m
rook-ceph-mon1-qfn6h              1/1       Running   0          1m
rook-ceph-osd-pj4wk               1/1       Running   0          1m
rook-ceph-osd-q4xp7               1/1       Running   0          1m

ROOK Storage Class設定

ここまででバックエンドストレージが出来上がっていますので、最後にストレージクラスをKubernetesに設定します。

$ kubectl create -f rook-storageclass.yaml

作成確認します。

$ kubectl get storageclass
NAME                   PROVISIONER     AGE
rook-block (default)   rook.io/block   1m

規定のストレージクラス設定

Kubernetesでは永続ボリューム要求をする際にストレージクラスを指定します。

例えば以下のように

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  storageClassName: rook-block
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi

ところがyamlファイルの作りによってはstorageClassNameを指定していないものもあります。

後述するhelmによるspinnakerのデプロイもその一つでした。

したがって、明示的なストレージの指定がない場合の動作を規定しておく必要があります。

$ kubectl get storageclass
NAME         PROVISIONER     AGE
rook-block   rook.io/block   1m

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

$ kubectl get storageclass
NAME                   PROVISIONER     AGE
rook-block (default)   rook.io/block   3m

ストレージクラス名の横に(default)が付与されていればOKです。

Spinnakerのデプロイ

helmインストール

Spinnakerのデプロイにはkubernetesパッケージマネージャーhelmを使用して行います。

コマンド単体で公開されているので、PATHが通っている場所に展開します。

$ wget https://storage.googleapis.com/kubernetes-helm/helm-v2.8.2-linux-amd64.tar.gz
$ tar zxvf helm-v2.8.2-linux-amd64.tar.gz
$ sudo cp linux-amd64/helm /bin/

helm初期化

helmを初期化します

$ helm init

deploymentsにtiller-deployが作成されているかを確認します。

kubectl get deployments --namespace=kube-system
NAME                             DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
heapster-v1.5.2                  1         1         1            1           10d
kube-dns                         1         1         1            1           10d
kubernetes-dashboard             1         1         1            1           10d
metrics-server-v0.2.1            1         1         1            1           10d
monitoring-influxdb-grafana-v4   1         1         1            1           10d
tiller-deploy                    1         1         1            1           10d

helmリポジトリアップデート

helmのリポジトリをアップデートします。

アップデートしない場合のstable/spinnaker CHART VERSIONは0.4.0になっており、

当該バージョンではデプロイに失敗していました。

当方の環境ではCHART VERSION 0.4.1でデプロイと動作確認しています。

$ helm search spinnaker
NAME                CHART VERSION   APP VERSION DESCRIPTION                                       
stable/spinnaker    0.4.0           1.6.0       Open source, multi-cloud continuous delivery pl...

$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈ 

$ helm search spinnaker
NAME                CHART VERSION   APP VERSION DESCRIPTION                                       
stable/spinnaker    0.4.1           1.6.0       Open source, multi-cloud continuous delivery pl...

Spinnakerデプロイ

helmコマンドでspinnakerをデプロイします。

$ helm install stable/spinnaker

プロンプトが返却されるのに時間がかかりますので、しばらく待ちます。

WEB UI への接続

デプロイしたSpinnakerのWEB UIにアクセスします。

方法は様々ですが、一番容易な方法としてローカルPCへkubectlをインストールし、port-forwardで接続することにしました。

ローカルPCはmacにて作業しています。

$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl
$ chmod 755 kubectl
$ mkdir .kube

kubectlがkubernetesクラスタへ接続するために、.kubeディレクトリ内へconfigという名前のコンフィグファイルを、既にkubectlが動作している環境(Masterノード等)からコピーしてください。

$ pwd
/Users/user/.kube

$ ls -l
total 8
-rw-r--r--   1 user  user  1961  5 11 17:29 config

kubectlが実行可能か、ポッドの一覧を取得して確認します。

$ ./kubectl get pod
NAME                                                   READY     STATUS      RESTARTS   AGE
default-http-backend-2wz6k                             1/1       Running     0          10d
nginx-ingress-kubernetes-worker-controller-6jr2k       1/1       Running     0          10d
nginx-ingress-kubernetes-worker-controller-twgsw       1/1       Running     0          10d
wishing-camel-create-bucket-hl5d7                      0/1       Completed   2          6d
wishing-camel-jenkins-6f7c98bf8f-xg2nx                 1/1       Running     0          5d
wishing-camel-minio-6cffcc6db8-vz4tw                   1/1       Running     0          6d
wishing-camel-redis-67968d58bf-9tnkm                   1/1       Running     0          6d
wishing-camel-spinnaker-clouddriver-6849758946-tnlgt   1/1       Running     0          6d
wishing-camel-spinnaker-deck-94bd44446-gllp7           1/1       Running     0          6d
wishing-camel-spinnaker-echo-6c8d594b58-d9g5l          1/1       Running     0          6d
wishing-camel-spinnaker-front50-5c96c6fb8-sxfsz        1/1       Running     0          6d
wishing-camel-spinnaker-gate-5bbb8f5f44-4g8vc          1/1       Running     0          6d
wishing-camel-spinnaker-igor-76f485774f-x5fjd          1/1       Running     0          6d
wishing-camel-spinnaker-orca-5d55699b75-p4vl7          1/1       Running     0          6d
wishing-camel-spinnaker-rosco-fbb775b59-z7pqf          1/1       Running     0          6d
wishing-camel-upload-build-image-dx295                 0/1       Completed   0          6d
wishing-camel-upload-run-pipeline-z8lpf                0/1       Completed   0          6d
wishing-camel-upload-run-script-bdllh                  0/1       Completed   0          6d

spinnaker-deck というポッドにport-forwardします。

$ ./kubectl port-forward wistful-crab-spinnaker-deck-74976bbcf9-x48dc 9000

WEBブラウザを開き、ポートフォワードしたローカルホストに対し接続します。

f:id:virtualtech:20180523134012p:plain

今回はひとまずここまで!

次回はSpinnakerのパイプライン設定を行い、Kubernetesへの自動デプロイを実行していきます。

JUJU + MAAS を使用したOpenstack構築 + 監視設定

JUJU + MAASによるOpenstackの構築と、 Gnocchi Grafanaを使用したリソース監視の作成に挑戦しました。

今回の筆者は コムシス情報システム株式会社 山本 聡(やまもと さとる)です。 以前のエントリーでKubernetesの構築について書かせていただきました。 http://tech.virtualtech.jp/entry/2018/03/29/104216

実行環境

構成はk8s構築で使用した物理サーバを流用します。 また、Openstack外部接続用のネットワークを追加しました。

f:id:virtualtech:20180424130921p:plain

MAASのインストール

MAAS、Ubuntuのインストールを実施します。

以下の設定でUbuntu Serverをインストール

  • インストールモード:Install Ubuntu Server(通常のインストール)

  • Language:日本語

  • Timezone:日本

  • Keyboard:Japanese

  • ソフトウェアの選択:OpenSSH Server

ネットワーク設定からMAAS+JUJUのインストール

JUJUのインストールまで以前のエントリーと同様です。 http://tech.virtualtech.jp/entry/2018/03/29/104216

MAASで認識された各サーバにタグを付与

Openstackのデプロイ用にMAAS上の各ノードにタグを付与します。

  • node01

 コントローラーノードとして設定

  • node02

 コンピュート及びcephノードとして設定

  • node03

 コンピュート及びcephノードとして設定

Openstackデプロイ

Openstackデプロイ用yamlファイルの作成

Openstack自動構築用のyamlファイルを用意します。 "tags="で記述される箇所がMAASのタグと一致するよう注意してください。

$ vim openstack-telemetry.yaml
machines:
  '0':
    series: xenial
    constraints: "arch=amd64 tags=node01"
  '1':
    series: xenial
    constraints: "arch=amd64 tags=node02"
  '2':
    series: xenial
    constraints: "arch=amd64 tags=node03"
relations:
- - nova-compute:amqp
  - rabbitmq-server:amqp
- - neutron-gateway:amqp
  - rabbitmq-server:amqp
- - keystone:shared-db
  - mysql:shared-db
- - nova-cloud-controller:identity-service
  - keystone:identity-service
- - glance:identity-service
  - keystone:identity-service
- - neutron-api:identity-service
  - keystone:identity-service
- - neutron-openvswitch:neutron-plugin-api
  - neutron-api:neutron-plugin-api
- - neutron-api:shared-db
  - mysql:shared-db
- - neutron-api:amqp
  - rabbitmq-server:amqp
- - neutron-gateway:neutron-plugin-api
  - neutron-api:neutron-plugin-api
- - glance:shared-db
  - mysql:shared-db
- - glance:amqp
  - rabbitmq-server:amqp
- - nova-cloud-controller:image-service
  - glance:image-service
- - nova-compute:image-service
  - glance:image-service
- - nova-cloud-controller:cloud-compute
  - nova-compute:cloud-compute
- - nova-cloud-controller:amqp
  - rabbitmq-server:amqp
- - nova-cloud-controller:quantum-network-service
  - neutron-gateway:quantum-network-service
- - nova-compute:neutron-plugin
  - neutron-openvswitch:neutron-plugin
- - neutron-openvswitch:amqp
  - rabbitmq-server:amqp
- - openstack-dashboard:identity-service
  - keystone:identity-service
- - nova-cloud-controller:shared-db
  - mysql:shared-db
- - nova-cloud-controller:neutron-api
  - neutron-api:neutron-api
- - cinder:image-service
  - glance:image-service
- - cinder:amqp
  - rabbitmq-server:amqp
- - cinder:identity-service
  - keystone:identity-service
- - cinder:cinder-volume-service
  - nova-cloud-controller:cinder-volume-service
- - cinder-ceph:storage-backend
  - cinder:storage-backend
- - ceph-mon:client
  - nova-compute:ceph
- - nova-compute:ceph-access
  - cinder-ceph:ceph-access
- - cinder:shared-db
  - mysql:shared-db
- - ceph-mon:client
  - cinder-ceph:ceph
- - ceph-mon:client
  - glance:ceph
- - ceph-osd:mon
  - ceph-mon:osd
- - ntp:juju-info
  - nova-compute:juju-info
- - ntp:juju-info
  - neutron-gateway:juju-info
- - ceph-radosgw:mon
  - ceph-mon:radosgw
- - ceph-radosgw:identity-service
  - keystone:identity-service
- - ceilometer:amqp
  - rabbitmq-server:amqp
- - ceilometer-agent:ceilometer-service
  - ceilometer:ceilometer-service
- - ceilometer:identity-notifications
  - keystone:identity-notifications
- - ceilometer-agent:nova-ceilometer
  - nova-compute:nova-ceilometer
- - aodh:shared-db
  - mysql:shared-db
- - aodh:identity-service
  - keystone:identity-service
- - aodh:amqp
  - rabbitmq-server:amqp
- - gnocchi:storage-ceph
  - ceph-mon:client
- - gnocchi:shared-db
  - mysql:shared-db
- - gnocchi:amqp
  - rabbitmq-server:amqp
- - gnocchi:coordinator-memcached
  - memcached:cache
- - gnocchi:metric-service
  - ceilometer:metric-service
- - gnocchi:identity-service
  - keystone:identity-service
- - ceilometer:identity-credentials
  - keystone:identity-credentials

series: xenial
services:
  aodh:
    annotations:
      gui-x: '1500'
      gui-y: '0'
    charm: cs:aodh-12
    num_units: 1
    options:
      openstack-origin: cloud:xenial-queens
    to:
    - lxd:0
  ceilometer:
    annotations:
      gui-x: '1250'
      gui-y: '0'
    charm: cs:ceilometer-252
    num_units: 1
    options:
      openstack-origin: cloud:xenial-queens
    to:
    - lxd:2
  ceilometer-agent:
    annotations:
      gui-x: '1250'
      gui-y: '500'
    charm: cs:ceilometer-agent-243
    num_units: 0
  ceph-mon:
    annotations:
      gui-x: '750'
      gui-y: '500'
    charm: cs:ceph-mon-23
    num_units: 2
    options:
      expected-osd-count: 2
      monitor-count: 2
      source: cloud:xenial-queens
    to:
    - lxd:1
    - lxd:2
  ceph-osd:
    annotations:
      gui-x: '1000'
      gui-y: '500'
    charm: cs:ceph-osd-257
    num_units: 2
    options:
      osd-devices: /dev/sdb
      osd-reformat: 'yes'
      source: cloud:xenial-queens
    to:
    - '1'
    - '2'
  ceph-radosgw:
    annotations:
      gui-x: '1000'
      gui-y: '250'
    charm: cs:ceph-radosgw-256
    num_units: 1
    options:
      source: cloud:xenial-queens
    to:
    - lxd:1
  cinder:
    annotations:
      gui-x: '750'
      gui-y: '0'
    charm: cs:cinder-268
    num_units: 1
    options:
      block-device: None
      glance-api-version: 2
      openstack-origin: cloud:xenial-queens
      worker-multiplier: 0.25
    to:
    - lxd:2
  cinder-ceph:
    annotations:
      gui-x: '750'
      gui-y: '250'
    charm: cs:cinder-ceph-231
    num_units: 0
  glance:
    annotations:
      gui-x: '250'
      gui-y: '0'
    charm: cs:glance-263
    num_units: 1
    options:
      openstack-origin: cloud:xenial-queens
      worker-multiplier: 0.25
    to:
    - lxd:2
  gnocchi:
    annotations:
      gui-x: '1500'
      gui-y: '250'
    charm: cs:gnocchi-7
    num_units: 1
    options:
      openstack-origin: cloud:xenial-queens
    to:
    - lxd:0
  keystone:
    annotations:
      gui-x: '500'
      gui-y: '0'
    charm: cs:keystone-275
    num_units: 1
    options:
      admin-password: openstack
      openstack-origin: cloud:xenial-queens
      worker-multiplier: 0.25
    to:
    - lxd:1
  memcached:
    annotations:
      gui-x: '1500'
      gui-y: '500'
    charm: cs:memcached-18
    num_units: 1
    to:
    - lxd:2
  mysql:
    annotations:
      gui-x: '0'
      gui-y: '250'
    charm: cs:percona-cluster-259
    num_units: 1
    options:
      innodb-buffer-pool-size: 256M
      max-connections: 1000
    to:
    - lxd:2
  neutron-api:
    annotations:
      gui-x: '500'
      gui-y: '500'
    charm: cs:neutron-api-258
    num_units: 1
    options:
      flat-network-providers: physnet1
      neutron-security-groups: true
      openstack-origin: cloud:xenial-queens
      worker-multiplier: 0.25
    to:
    - lxd:2
  neutron-gateway:
    annotations:
      gui-x: '0'
      gui-y: '0'
    charm: cs:neutron-gateway-247
    num_units: 1
    options:
      bridge-mappings: physnet1:br-ex
      data-port: br-ex:eno2
      openstack-origin: cloud:xenial-queens
      worker-multiplier: 0.25
    to:
    - '0'
  neutron-openvswitch:
    annotations:
      gui-x: '250'
      gui-y: '500'
    charm: cs:neutron-openvswitch-249
    num_units: 0
  nova-cloud-controller:
    annotations:
      gui-x: '0'
      gui-y: '500'
    charm: cs:nova-cloud-controller-306
    num_units: 1
    options:
      network-manager: Neutron
      openstack-origin: cloud:xenial-queens
      worker-multiplier: 0.25
    to:
    - lxd:0
  nova-compute:
    annotations:
      gui-x: '250'
      gui-y: '250'
    charm: cs:nova-compute-282
    num_units: 2
    options:
      config-flags: default_ephemeral_format=ext4
      enable-live-migration: true
      enable-resize: true
      migration-auth-type: ssh
      openstack-origin: cloud:xenial-queens
    to:
    - '1'
    - '2'
  ntp:
    annotations:
      gui-x: '1000'
      gui-y: '0'
    charm: cs:ntp-24
    num_units: 0
  openstack-dashboard:
    annotations:
      gui-x: '500'
      gui-y: '-250'
    charm: cs:openstack-dashboard-257
    num_units: 1
    options:
      openstack-origin: cloud:xenial-queens
    to:
    - lxd:0
  rabbitmq-server:
    annotations:
      gui-x: '500'
      gui-y: '250'
    charm: cs:rabbitmq-server-72
    num_units: 1
    to:
    - lxd:0
  grafana:
    charm: "cs:xenial/grafana-9"
    num_units: 1
    options:
      install_method: apt
      admin_password: password5656
    annotations:
      gui-x: '132.75'
      gui-y: '1066'
    to:
      - lxd:1

設定値のポイント解説

  • osd-devices: /dev/sdb

ceph-osdが設定されるサーバはディスクが2つ設定されている必要があります。

  • bridge-mappings: physnet1:br-ex
  • data-port: br-ex:eno2

サーバが認識するネットワークポートと一致させる必要があります。

  • config-flags: default_ephemeral_format=ext4

今回はext4で構成しますが、異なるファイルシステムも使用することが出来ます。

juju deploy を実行

$ juju deploy openstack-telemetry.yaml

openstackclientをインストール

Openstackを操作するためのクライアントをインストールします。

$ sudo apt install python-openstackclient

novarc_autoファイルの作成

Openstackクライアントの動作に必要なrcファイルを作成します

$ vim novarc_auto
_OS_PARAMS=$(env | awk 'BEGIN {FS="="} /^OS_/ {print $1;}' | paste -sd ' ')
for param in $_OS_PARAMS; do
    unset $param
done
unset _OS_PARAMS

keystone_unit=$(juju status keystone|grep -i workload -A1|tail -n1|awk '{print $1}'|tr -d '*')
echo Keystone unit: ${keystone_unit}
if ! curl -qs `juju run --unit ${keystone_unit} "unit-get private-address"`:5000/v3 | grep 404 ;
then
echo Using keystone v3 api
export OS_AUTH_URL=${OS_AUTH_PROTOCOL:-http}://`juju run --unit ${keystone_unit} "unit-get private-address"`:5000/v3
export OS_USERNAME=admin
export OS_PASSWORD=openstack
export OS_DOMAIN_NAME=admin_domain
export OS_USER_DOMAIN_NAME=admin_domain
export OS_PROJECT_DOMAIN_NAME=admin_domain
export OS_PROJECT_NAME=admin
export OS_REGION_NAME=RegionOne
export OS_IDENTITY_API_VERSION=3
# Swift needs this:
export OS_AUTH_VERSION=3
else
echo Using keystone v2 api
export OS_USERNAME=admin
export OS_PASSWORD=openstack
export OS_TENANT_NAME=admin
export OS_REGION_NAME=RegionOne
export OS_AUTH_URL=${OS_AUTH_PROTOCOL:-http}://`juju run --unit ${keystone_unit} "unit-get private-address"`:5000/v2.0

rcファイルの読み込み

$ source novarc_auto

監視確認

Openstack状態の確認

デプロイがすべて完了したことをjuju statusコマンドで確認後、 実際にOpenstackが操作できるかをコマンドで打ってみましょう。

$ openstack service list

+----------------------------------+-----------+--------------+
| ID                               | Name      | Type         |
+----------------------------------+-----------+--------------+
| 0a6aac12ae71496494a3b0748e17021b | aodh      | alarming     |
| 2447939369d74795a0ee65bda1bfe3a1 | swift     | object-store |
| 4a1d931905fd43c3bf3403eb25a96bd4 | placement | placement    |
| 4a40e8739f9046d7978422e5331c8383 | gnocchi   | metric       |
| 5586181aad80426392ee40c41ab9b19a | keystone  | identity     |
| 56608d839340422a8b02af265de70ce9 | neutron   | network      |
| 5e1abf112ca44857a347ba0bd471720e | nova      | compute      |
| 671b147f8d06417fa1b9ff14475d0f40 | glance    | image        |
| 9820e33afcf3467da93a7138632ea917 | cinderv3  | volumev3     |
| bfb458d163314bdeaefd1e867de2ead9 | cinderv2  | volumev2     |
+----------------------------------+-----------+--------------+

上記のような応答があれば成功です。

Ceilometerを有効化

今回の構成のようにCeilometerとGnocchiを連携する場合、 以下のコマンドで有効化する必要があります。

$ juju run-action ceilometer/0 ceilometer-upgrade

Dashboardにアクセス

Dashboardにアクセスします。 juju statusコマンドでopenstack-dashboardのIPアドレスを確認し、 http://xx.xx.xx.xx/horizon のURLでブラウザからアクセスしてください。 yamlファイルに認証の変更を加えていなければ、以下の情報でログインできます。

  • ドメイン:admin_domain
  • ユーザ名:admin
  • パスワード:openstack

インスタンスの作成

監視データの蓄積をするため、監視対象となるインスタンスの作成を行います。 jujuで構築したOpenstackは初期状態においてインスタンス作成に必要な下記項目が設定されていないので、 Dashboardから設定を行ってください。

  • イメージの登録
  • ルーターの作成
  • 内部ネットワークの作成
  • 内部ネットワークサブネットの作成
  • フレーバーの作成

CirrOS等の軽量イメージを使用してインスタンスを稼働しましょう。

メトリックデータの取得

実際に監視データが取得できているかを確認します。 まずはリソースの一覧から表示してみましょう。

$ openstack metric resource list

+--------------------------------------+----------------------------+----------------------------------+----------------------------------+-----------------------------------------------------------------------+----------------------------------+----------+----------------------------------+--------------+-------------------------------------------------------------------+
| id                                   | type                       | project_id                       | user_id                          | original_resource_id                                                  | started_at                       | ended_at | revision_start                   | revision_end | creator                                                           |
+--------------------------------------+----------------------------+----------------------------------+----------------------------------+-----------------------------------------------------------------------+----------------------------------+----------+----------------------------------+--------------+-------------------------------------------------------------------+
| 579ef430-4278-51ca-ad28-4edd6897fee6 | instance_network_interface | d948eedc6f1047bfaf9539e6526dd919 | 8981ef1a6dcb4683b97b82925fd87a9f | instance-00000001-bd7f158f-369c-41dc-81b1-84487a583c6e-tap91c4a8f8-9d | 2018-04-24T01:12:48.853092+00:00 | None     | 2018-04-24T01:12:48.853151+00:00 | None         | 1cf01d45402749e286c098b89caf5eaf:5f5d5bfecb944273a00697e59eafc436 |
| b39ba0c5-860f-598a-b0f4-82ab5afb5c50 | instance_disk              | d948eedc6f1047bfaf9539e6526dd919 | 8981ef1a6dcb4683b97b82925fd87a9f | bd7f158f-369c-41dc-81b1-84487a583c6e-vdb                              | 2018-04-24T01:12:48.941122+00:00 | None     | 2018-04-24T01:12:48.941142+00:00 | None         | 1cf01d45402749e286c098b89caf5eaf:5f5d5bfecb944273a00697e59eafc436 |
| 3f830f79-7cf9-5060-affd-5e4d43196ffc | instance_disk              | d948eedc6f1047bfaf9539e6526dd919 | 8981ef1a6dcb4683b97b82925fd87a9f | bd7f158f-369c-41dc-81b1-84487a583c6e-vda                              | 2018-04-24T01:12:49.119669+00:00 | None     | 2018-04-24T01:12:49.119688+00:00 | None         | 1cf01d45402749e286c098b89caf5eaf:5f5d5bfecb944273a00697e59eafc436 |
| bd7f158f-369c-41dc-81b1-84487a583c6e | instance                   | d948eedc6f1047bfaf9539e6526dd919 | 8981ef1a6dcb4683b97b82925fd87a9f | bd7f158f-369c-41dc-81b1-84487a583c6e                                  | 2018-04-24T01:17:48.346622+00:00 | None     | 2018-04-24T01:17:48.346641+00:00 | None         | 1cf01d45402749e286c098b89caf5eaf:5f5d5bfecb944273a00697e59eafc436 |
+--------------------------------------+----------------------------+----------------------------------+----------------------------------+-----------------------------------------------------------------------+----------------------------------+----------+----------------------------------+--------------+-------------------------------------------------------------------+

次にリソースの詳細です、instanceの詳細を表示してみます。

$ openstack metric resource show bd7f158f-369c-41dc-81b1-84487a583c6e

+-----------------------+---------------------------------------------------------------------+
| Field                 | Value                                                               |
+-----------------------+---------------------------------------------------------------------+
| created_by_project_id | 5f5d5bfecb944273a00697e59eafc436                                    |
| created_by_user_id    | 1cf01d45402749e286c098b89caf5eaf                                    |
| creator               | 1cf01d45402749e286c098b89caf5eaf:5f5d5bfecb944273a00697e59eafc436   |
| ended_at              | None                                                                |
| id                    | bd7f158f-369c-41dc-81b1-84487a583c6e                                |
| metrics               | compute.instance.booting.time: 41aa5d18-772c-49ef-b1f6-26ce117d9290 |
|                       | cpu.delta: f76b4f69-8a30-4565-95af-bbf5f080cb85                     |
|                       | cpu: f9e7437c-6346-49d1-bd93-e82320afad90                           |
|                       | cpu_l3_cache: fbbb5723-f24a-4376-8253-c9b69768022c                  |
|                       | cpu_util: 5c22865d-6727-4c6c-ba3d-37a6d8ee6f4c                      |
|                       | disk.allocation: cb44a188-0d14-443e-aa4b-f85cc502d021               |
|                       | disk.capacity: 8fae7b4d-43f7-4357-8250-e7644e93cd3c                 |
|                       | disk.ephemeral.size: 07eb3f61-6faa-481d-aa4a-6d2e66591b05           |
|                       | disk.iops: 62c81e4c-fb7d-4d96-8657-9e9884dc3aef                     |
|                       | disk.latency: 17f8f246-36c4-4096-8901-c32d5a0ecf42                  |
|                       | disk.read.bytes.rate: 7a7776a4-abe9-4c81-8981-d2c560f52f12          |
|                       | disk.read.bytes: bbe8c0a0-a7bc-4c67-9c62-b500f20fa7d2               |
|                       | disk.read.requests.rate: 4508526a-af1c-4289-b340-0255f012c042       |
|                       | disk.read.requests: 36e36446-fc08-4e20-8d1e-ffaa5c8543c5            |
|                       | disk.root.size: 504a2693-5083-4c2b-9653-1394ad3ed415                |
|                       | disk.usage: 8afdfafe-e499-4c8a-b6e8-c9b60fb61504                    |
|                       | disk.write.bytes.rate: 422f0e51-1fca-4762-b418-c6de634601f7         |
|                       | disk.write.bytes: d21bd9f7-c441-4f95-ad0f-1e29fc799231              |
|                       | disk.write.requests.rate: 114edd3e-ce8e-4fd7-b96e-5fe2808de96e      |
|                       | disk.write.requests: 6e15ee6b-965d-46e7-82a7-bd869464c956           |
|                       | memory.bandwidth.local: a05a8117-2364-47a1-8eb8-4335227b89f2        |
|                       | memory.bandwidth.total: 6ea4ed00-bcca-49bc-96a7-a089577f08aa        |
|                       | memory.resident: 72564fd4-e3df-46f2-b2a5-0da106e749ce               |
|                       | memory.swap.in: 8c150408-0952-4a56-8c49-7bf196d3382d                |
|                       | memory.swap.out: ba87a10a-7e53-46e4-ba0d-40285990d5e6               |
|                       | memory.usage: 8c1008d1-a1ca-4986-8526-d95fd1a91886                  |
|                       | memory: 111aae74-7814-4228-b959-b0133b70a600                        |
|                       | perf.cache.misses: 76ac152a-cd9f-4593-be95-c586cc65471e             |
|                       | perf.cache.references: 44dae2f5-67b2-4509-8457-b6b512fee081         |
|                       | perf.cpu.cycles: 6fdcae77-b548-4cd9-b78f-fdbf5dc9057c               |
|                       | perf.instructions: 09357908-90bb-43bb-a74b-8cb86dda8833             |
|                       | vcpus: d1ad1631-b464-4179-95fe-573b2d83ee69                         |
| original_resource_id  | bd7f158f-369c-41dc-81b1-84487a583c6e                                |
| project_id            | d948eedc6f1047bfaf9539e6526dd919                                    |
| revision_end          | None                                                                |
| revision_start        | 2018-04-24T01:17:48.346641+00:00                                    |
| started_at            | 2018-04-24T01:17:48.346622+00:00                                    |
| type                  | instance                                                            |
| user_id               | 8981ef1a6dcb4683b97b82925fd87a9f                                    |
+-----------------------+---------------------------------------------------------------------+

監視の値を取得してみます、memory.usageの表示です。

$ openstack metric measures show 8c1008d1-a1ca-4986-8526-d95fd1a91886
+---------------------------+-------------+-------+
| timestamp                 | granularity | value |
+---------------------------+-------------+-------+
| 2018-04-24T10:15:00+09:00 |       300.0 |  45.0 |
+---------------------------+-------------+-------+

Grafanaによるグラフ化

GrafanaへGnocchi-Pluginのインストール

コマンドラインで監視データが取得できたので、今度はグラフ化に挑みます。 まずはGrafanaへGnocchiをデータソースとして利用するためのプラグインを入れます。

$ juju ssh grafana/0
$ sudo grafana-cli plugins install gnocchixyz-gnocchi-datasource
installing gnocchixyz-gnocchi-datasource @ 1.7.0
from url: https://grafana.com/api/plugins/gnocchixyz-gnocchi-datasource/versions/1.7.0/download
into: /var/lib/grafana/plugins

Failed downloading. Will retry once.
✔ Installed gnocchixyz-gnocchi-datasource successfully 

Restart grafana after installing plugins . <service grafana-server restart>

Grafanaサービスをリスタートし、Grafanaサーバから抜けます。

$ sudo service grafana-server restart
$ exit

Grafanaログイン情報の表示

Grafanaへログインするためのパスワードを表示します。

unit-grafana-0:
  id: 57b95cec-fb95-43e1-86c5-86ad0f274c33
  results:
    password: password5656
  status: completed
  timing:
    completed: 2018-04-24 01:29:02 +0000 UTC
    enqueued: 2018-04-24 01:29:01 +0000 UTC
    started: 2018-04-24 01:29:02 +0000 UTC
  unit: grafana/0

Grafanaへ接続

Grafanaデプロイ先アドレスにWebアクセスします。 ユーザ名はadmin、パスワードは上記で表示されたものを使用します。

http://xx.xx.xx.xx:3000

Data Sourceの設定

続いてData Sourceを設定します。 Gnocchi-Pluginがインストールされていれば、TypeにGnocchiを選択できます。

f:id:virtualtech:20180424131020p:plain

しかしここで問題発生、Gnocchiへの認証モードがkeystoneを使用すると通りません。

f:id:virtualtech:20180424131023p:plain

どうやらCORSを設定しなければならないようですが、jujuの設定項目で解決ができない範囲のようです。

https://blog.sileht.net/configuring-cors-for-gnocchi-and-keystone.html

何とか出来ないものかとpluginの設定項目を探すと、認証にtokenを指定できるのを見つけました。 強引ですが、tokenを引っ張ってきて設定してみます。

$ curl -i -X POST -H "Content-Type: application/json" -d '
{
  "auth": {
    "identity": {
      "methods": [
        "password"
      ],
      "password": {
        "user": {
          "name": "admin",
          "password": "openstack",
          "domain": {
            "name": "admin_domain"
          }
        }
      }
    },
    "scope": {
      "project": {
        "name": "admin",
        "domain": {
          "name": "admin_domain"
        }
      }
    }
  }
}' http://172.17.30.89:5000/v3/auth/tokens

HTTP/1.1 201 Created
Date: Tue, 24 Apr 2018 01:42:59 GMT
Server: Apache/2.4.18 (Ubuntu)
X-Subject-Token: 830d04244cfc4598b8c5cbb4f2fdc31c
Vary: X-Auth-Token
X-Distribution: Ubuntu
x-openstack-request-id: req-ceae75ca-0b04-4fe0-bb3e-cacb630a3a1c
Content-Length: 6557
Content-Type: application/json

{"token": {"is_domain": false, "methods": ["password"], "roles": [{"id": "097ec800c2f64a438a787f058066fbc3", "name": "Admin"}, {"id": "b6b8500fe90048e7b4700bea94ffdb31", "name": "Member"}], (省略)

tokenを渡した所、正常にData Sourceを設定できたようです。

f:id:virtualtech:20180424131025p:plain

グラフ化

設定したDeta Sourceを指定してグラフを表示させてみます。 metric IDを直接指定する方式で値が取れているのがわかります。

f:id:virtualtech:20180424131027p:plain

認証の問題

何とかグラフ化に成功しましたが、tokenの期限が切れてしまうとData Sourceが機能しなくなってしまうため、別の手法を模索する必要があると感じました。 引き続き調査したいと思います。

KubernetesでPodを作る方法

KubernetesでPodを作る方法は公式のドキュメント「Kubernetes 101」にまとめられていますし、Web検索すると同じような内容のブログ記事やフォーラムのFAQが見つかります。

多くの人がこんな内容のYAMLファイルを書いて、Nginx 1.7.9をデプロイしたはずです。 ちなみにDocker HubのNginxのタグ一覧を見る限り、1.13.11が最新です。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80

では、「Nginxではなく、FedoraとかUbuntuとかCentOSとか、そういった環境をPodを作って動かしたい時はどうするか」と思ったのでその方法を調査することから始めました。

ます確認したのはKubernetes公式のドキュメントです。隅々見たつもりですが、残念ながら有力な情報は見つけられませんでした。そこでさらに色々調べたところ、次のようなサイトの情報を見つけることができました。

foxutech.com

必要な部分だけ抜き出すと、「spec - containers」にDockerのイメージと次のような記述を入れると良いようです。勘が良いかたは気がつくと思いますが、この内容でPodを作った場合Podは1000秒経過すると、自動的に終了してしまいます。なんかあの作品っぽいですね。

...
spec:
  containers:
  - name: shell
    image: centos:7
    command:
      - "bin/bash"
      - "-c"
      - "sleep 1000"

これでは場合によっては困るので、他の方法を考えてみました。

apiVersion: v1
kind: Pod
metadata:
  name: fedora
spec:
  restartPolicy: OnFailure
  containers:
    - name: fedora
      image: fedora:latest
      command:
        - "/sbin/init"

コマンドとしてInitを実行してみました。このようなYAMLを書いてkubectl create -f deploy.ymlすると、Fedoraが起動するPodを作ることができます。一応、手元の環境ではうまく動いていました。

metadataにnameとしてPodの名前を記述しておくと、その名前でPodが作成されます。imagesはデフォルトではDocker Hubに登録されたイメージが使われます。

JUJU + MAAS を使用したKubernetes構築 + 監視設定

最近当ブログでよく書かれているKubernetes。 そのKubernetesについて、物理サーバを使用した構築を行ってみようと考えました。

今回はJUJU + MAASによるKubernetes(k8s)の構築と、 PrometheusとGrafanaを使用したリソース監視の環境を作成した結果を公開いたします。

JUJU、MAAS、Kubernetesの基本的な説明については以前のエントリーを参照ください。 http://tech.virtualtech.jp/entry/2018/02/05/124557

申し遅れました、今回の筆者を努めます コムシス情報システム株式会社 山本 聡(やまもと さとる)と申します。 現在、日本仮想化技術株式会社様の元でKubernetesを学習させていただいております。

実行環境

構成を単純化するため、JUJUデプロイまでは全て物理サーバでの構築を行いました。 JUJU+MAAS以外のサーバはJUJU+MAASが自動的にアドレスを割り当てます。 今回のKubernetes構築ではMatser1台、Worker2台で構成します。

f:id:virtualtech:20180329094428p:plain

MAASのインストール

MAASサーバのみ手動でUbuntuのインストールを実施します。

Ubuntu Serverをインストール

Ubuntu Serverについては、ベーシックなインストール設定とします。 インストール後にSSHで操作するため、OpenSSH Serverを選択しています。

  • インストールモード:Install Ubuntu Server(通常のインストール)

  • Language:日本語

  • Timezone:日本

  • Keyboard:Japanese

  • ソフトウェアの選択:OpenSSH Server

ネットワーク設定

まずはネットワーク設定を行いましょう。

vim /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# eno1
auto iface eno1 inet static
address 172.17.30.10
network 172.17.30.0
netmask 255.255.255.0
broadcast 172.17.30.255
gateway 172.17.30.1
dns-nameservers 8.8.8.8

インターフェース有効化のため再起動

$ sudo reboot

アップデート

$ sudo apt -y update
$ sudo apt -y upgrade

MAASインストール

$ sudo apt install maas

執筆時点でのバージョンは2.3.0がインストールされました。

MAASユーザ設定

MAASの管理者を作成します。 今回はmaasadminというユーザを作成しました。 import SSH keysは後で入力するため、ここではEnterでスキップしておきます。

$ sudo maas createadmin

Username:maasadmin
Password:maasadmin
Again:maasadmin
Email:
Import SSH keys [] (lp:user-id or gh:user-id):

MAASダッシュボードを開く

ここまで完了すると、MAASの管理画面を開くことができます。 Port:5240 へWEBブラウザからアクセスしてみましょう。 MAASユーザ設定で使用した情報を使ってログインします。

http://172.17.30.10:5240/MAAS

f:id:virtualtech:20180329094622p:plain

MAASネットワーク設定

次に、MAAS-DHCPが使用するアドレス帯(Reserve Dynamic range)と 優先的に使用しないアドレス帯(Reserve range)を設定します。

  • subnet → VLAN

  • Reserve dynamic range :172.17.30.240 - 254

  • Reserve range :172.17.30.1 - 50

f:id:virtualtech:20180329094653p:plain

MAAS鍵設定

画面右上のユーザ名からUser Preferencesを表示し、MAAS Keysを確認して控えておきます。 またMAASが使用するssh公開鍵をSSH keysに登録しましょう。 SSH keyはssh-keygen -t rsaを使用して予め作成しておきます。

f:id:virtualtech:20180329094734p:plain

各サーバをPXEブート

MAASのネットワーク設定が終わったら、各サーバをPXEで起動してみてください。 MAASからネットワークアドレスやミニマムなOSが自動的に配信され、enlistが行われます。 Nodesに表示された各サーバをコミッショニングし、StatusがReadyになればサーバの準備は完了です。

  • Enlist 自動

  • Commission MAASが認識したサーバを選択、Take Actionからコミッションする

JUJUのインストール

JUJUのインストールと、JUJU Bootstrapサーバを構築します。 コンポーネントのインストールとBootstrap用のyamlファイルを用意しましょう。

MAASサーバにJUJUをインストール

今回はMAASサーバにJUJUのインストールを行いました。

$ sudo snap instal juju --clasic

JUJU Bootstrapの作成

Bootstrap用のyamファイルを作成します。

$ vim mymaas.yaml

clouds:
   maas-server:
      type: maas
      auth-types: [oauth1]
      endpoint: http://172.17.30.10/MAAS/

用意したyamlファイルをjuju-addcloudして登録します。

$ juju add-cloud maas-server mymaas.yaml

maas-serverという名前でCloudが作成されているかを確認します。

$ juju clouds

Cloud        Regions  Default          Type        Description
aws               14  us-east-1        ec2         Amazon Web Services
aws-china          1  cn-north-1       ec2         Amazon China
aws-gov            1  us-gov-west-1    ec2         Amazon (USA Government)
azure             26  centralus        azure       Microsoft Azure
azure-china        2  chinaeast        azure       Microsoft Azure China
cloudsigma         5  hnl              cloudsigma  CloudSigma Cloud
google             9  us-east1         gce         Google Cloud Platform
joyent             6  eu-ams-1         joyent      Joyent Cloud
oracle             5  uscom-central-1  oracle      Oracle Compute Cloud Service
rackspace          6  dfw              rackspace   Rackspace Cloud
localhost          1  localhost        lxd         LXD Container Hypervisor
maas-server        0                   maas        Metal As A Service

MAASとの連携を行うためのMAAS keyをJUJUに登録します。

$ juju add-credential Zner4ZAkAMvsg6xcST:bGQsgV4ALNKj2wmh5u:tz7gGj7Rwc77vUSgq9CGdw5WGW5cPdAJ

MAAS keyが登録されたかを確認します。

$ juju credentials --format yaml --show-secrets

JUJU Bootstrapを作成します。

$ juju bootstrap --constraints tags=bootstrap maas-server maas

もしBootstrap作成に失敗した場合は、kill-controllerを使用して再実行してください。

$ juju kill-controller maas

juju guiの表示

Bootstrapの作成が完了すると、juju guiコマンドで表示されたURLにアクセスするとjujuのGUI画面を表示できます。

$ juju gui

GUI 2.12.1 for model "admin/default" is enabled at:
  https://172.17.30.56:17070/gui/u/admin/default
Your login credential is:
  username: admin
  password: 765b9d00e1cff4338b193767fd4c9db5

Kubernetesのデプロイ

Kubernetesを構築する準備が整いました。 ここまで構築すれば、JUJUとMAASが自動的にKubernetesを構築してくれます。

MAASで認識された各サーバにタグを付与

Kubernetesのデプロイ用にMAAS上の各ノードにタグを付与します。

  • master

  • worker01

  • worker02

f:id:virtualtech:20180329095022p:plain

Kubernetesデプロイ用yamlファイルの作成

Kubernetes自動構築用のyamlファイルを用意します。 "tags="で記述される箇所がMAASのタグと一致するよう注意してください。

$ vim bundle.yaml
description: A 2-machine Kubernetes cluster, appropriate for PoC. Includes a 1 Kubernetes worker nodes.
machines:
  '0':
    series: xenial
    constraints: "arch=amd64 tags=master"
  '1':
    series: xenial
    constraints: "arch=amd64 tags=worker01"
  '2':
    series: xenial
    constraints: "arch=amd64 tags=worker02"

services:
  easyrsa:
    annotations:
      gui-x: '450'
      gui-y: '550'
    charm: cs:~containers/easyrsa-27
    num_units: 1
    to:
    - lxd:0
  etcd:
    annotations:
      gui-x: '800'
      gui-y: '550'
    charm: cs:~containers/etcd-63
    num_units: 1
    to:
    - '0'
  flannel:
    annotations:
      gui-x: '450'
      gui-y: '750'
    charm: cs:~containers/flannel-40
  kubeapi-load-balancer:
    annotations:
      gui-x: '450'
      gui-y: '250'
    charm: cs:~containers/kubeapi-load-balancer-53
    expose: true
    num_units: 1
    options:
      proxy_read_timeout: 120
    to:
    - '0'
  kubernetes-master:
    annotations:
      gui-x: '800'
      gui-y: '850'
    charm: cs:~containers/kubernetes-master-89
    num_units: 1
    options:
      channel: 1.9/stable
    to:
    - '0'
  kubernetes-worker:
    annotations:
      gui-x: '100'
      gui-y: '850'
    charm: cs:~containers/kubernetes-worker-106
    expose: true
    num_units: 2
    options:
      channel: 1.9/stable
    to:
    - '1'
    - '2'
relations:
- - kubernetes-master:kube-api-endpoint
  - kubeapi-load-balancer:apiserver
- - kubernetes-master:loadbalancer
  - kubeapi-load-balancer:loadbalancer
- - kubernetes-master:kube-control
  - kubernetes-worker:kube-control
- - kubernetes-master:certificates
  - easyrsa:client
- - etcd:certificates
  - easyrsa:client
- - kubernetes-master:etcd
  - etcd:db
- - kubernetes-worker:certificates
  - easyrsa:client
- - kubernetes-worker:kube-api-endpoint
  - kubeapi-load-balancer:website
- - kubeapi-load-balancer:certificates
  - easyrsa:client
- - flannel:etcd
  - etcd:db
- - flannel:cni
  - kubernetes-master:cni
- - flannel:cni
  - kubernetes-worker:cni

JUJUにk8sのモデルを作成

$ juju add-model k8s
$ juju switch k8s
$ juju models

Controller: maas

Model       Cloud/Region  Status     Machines  Cores  Access  Last connection
controller  maas-server   available         1     16  admin   just now
default     maas-server   available         0      -  admin   2018-03-26
k8s*        maas-server   available         7     80  admin   4 hours ago

アフタリスクがついている場所が、現在選択されているモデルです。

juju deploy を実行

$ juju deploy bundle.yaml

デプロイ中のログ表示

$ juju debug-log

yamlファイルの記述ミスなどのデプロイ失敗時

デプロイに失敗したなどでやり直す場合、デプロイ先のモデルを再作成して実行しましょう。

$ juju destroy-model k8s
$ juju add-model k8s
$ juju switch k8s

kubectlをインストール

Kubernetesを操作するためのクライアントをインストールします。

$ sudo snap install kubectl --classic

kubernetesコンフィグファイルのコピー

$ mkdir ~/.kube && touch ~/.kube/config
$ juju scp kubernetes-master/0:config ~/.kube/config

kubernetes状態の確認

実際にKubernetesが操作できるかをコマンドで確認してみましょう。

$ kubectl cluster-info

Kubernetes master is running at https://172.17.30.58:443
Heapster is running at https://172.17.30.58:443/api/v1/namespaces/kube-system/services/heapster/proxy
KubeDNS is running at https://172.17.30.58:443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
kubernetes-dashboard is running at https://172.17.30.58:443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy
Grafana is running at https://172.17.30.58:443/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy
InfluxDB is running at https://172.17.30.58:443/api/v1/namespaces/kube-system/services/monitoring-influxdb:http/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

「kubernetes-dashboard is running at...」に表示されるURLがKubernetesのダッシュボードです。 アクセス時は認証を要求されるため、configの閲覧し、アカウントを控えておきます。

$ kubectl config view

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://172.17.30.58:443
  name: juju-cluster
contexts:
- context:
    cluster: juju-cluster
    user: admin
  name: juju-context
current-context: juju-context
kind: Config
preferences: {}
users:
- name: admin
  user:
    password: chVha5l96WG9720cKwgHpDJuduJpdtGQ
    username: admin

Prometheus + AlertManager + Grafanaのデプロイ

PrometheusとGrafanaをデプロイします。 既にデプロイした環境に追加するため、yamlファイルをコピーして編集します。 元のyamlファイルに追記する形で各コンポーネントを記述します。 既にデプロイ済みの環境に対し、追加でデプロイを行う場合は元のyamlにコンポーネントを追記する形を取りましょう。 分けてしまうと、JUJUはまだ使われていないマシンを探し出してデプロイしようとしてしまいます。

$ cp -p bundle.yaml cp -p bundle.yaml addmonitor.yaml
$ vim addmonitor.yaml
description: A 2-machine Kubernetes cluster, appropriate for PoC. Includes a 1 Kubernetes worker nodes.
machines:
  '0':
    series: xenial
    constraints: "arch=amd64 tags=master"
  '1':
    series: xenial
    constraints: "arch=amd64 tags=worker01"
  '2':
    series: xenial
    constraints: "arch=amd64 tags=worker02"

services:
  easyrsa:
    annotations:
      gui-x: '450'
      gui-y: '550'
    charm: cs:~containers/easyrsa-27
    num_units: 1
    to:
    - lxd:0
  etcd:
    annotations:
      gui-x: '800'
      gui-y: '550'
    charm: cs:~containers/etcd-63
    num_units: 1
    to:
    - '0'
  flannel:
    annotations:
      gui-x: '450'
      gui-y: '750'
    charm: cs:~containers/flannel-40
  kubeapi-load-balancer:
    annotations:
      gui-x: '450'
      gui-y: '250'
    charm: cs:~containers/kubeapi-load-balancer-53
    expose: true
    num_units: 1
    options:
      proxy_read_timeout: 120
    to:
    - '0'
  kubernetes-master:
    annotations:
      gui-x: '800'
      gui-y: '850'
    charm: cs:~containers/kubernetes-master-89
    num_units: 1
    options:
      channel: 1.9/stable
    to:
    - '0'
  kubernetes-worker:
    annotations:
      gui-x: '100'
      gui-y: '850'
    charm: cs:~containers/kubernetes-worker-106
    expose: true
    num_units: 2
    options:
      channel: 1.9/stable
    to:
    - '1'
    - '2'
  prometheus:
    charm: "cs:xenial/prometheus-5"
    num_units: 1
    storage metrics-filesystem: rootfs
    options:
      install_method: snap
      web-listen-port: 9090
      static-targets: 172.17.30.62:9100
    annotations:
      gui-x: '422.25006103515625'
      gui-y: '1066.5'
    to:
      - lxd:0
  grafana:
    charm: "cs:xenial/grafana-9"
    num_units: 1
    options:
      install_method: apt
      admin_password: password5656
    annotations:
      gui-x: '132.75'
      gui-y: '1066'
    to:
      - lxd:0
  prometheus-alertmanager:
    charm: "cs:xenial/prometheus-alertmanager-2"
    num_units: 1
    options:
      snap_channel: stable
      port: 9093
    annotations:
      gui-x: '720.2500610351562'
      gui-y: '1076.5'
    to:
      - lxd:0

relations:
- - kubernetes-master:kube-api-endpoint
  - kubeapi-load-balancer:apiserver
- - kubernetes-master:loadbalancer
  - kubeapi-load-balancer:loadbalancer
- - kubernetes-master:kube-control
  - kubernetes-worker:kube-control
- - kubernetes-master:certificates
  - easyrsa:client
- - etcd:certificates
  - easyrsa:client
- - kubernetes-master:etcd
  - etcd:db
- - kubernetes-worker:certificates
  - easyrsa:client
- - kubernetes-worker:kube-api-endpoint
  - kubeapi-load-balancer:website
- - kubeapi-load-balancer:certificates
  - easyrsa:client
- - flannel:etcd
  - etcd:db
- - flannel:cni
  - kubernetes-master:cni
- - flannel:cni
  - kubernetes-worker:cni
- - grafana:grafana-source
  - prometheus:grafana-source
- - prometheus-alertmanager:alertmanager-service
  - prometheus:alertmanager-service

デプロイを実行

$ juju deploy addmonitor.yaml

以下のコマンドでデプロイの状態を表示できます。 各コンンポーネントがどのIPアドレスを使用しているかも分かるため、デプロイ後には一度チェックをしておきましょう。

$ juju status

Model  Controller  Cloud/Region  Version  SLA
k8s    maas        maas-server   2.3.4    unsupported

App                      Version  Status  Scale  Charm                    Store       Rev  OS      Notes
easyrsa                  3.0.1    active      1  easyrsa                  jujucharms   27  ubuntu  
etcd                     2.3.8    active      1  etcd                     jujucharms   63  ubuntu  
flannel                  0.9.1    active      3  flannel                  jujucharms   40  ubuntu  
grafana                           active      1  grafana                  jujucharms    9  ubuntu  
kubeapi-load-balancer    1.10.3   active      1  kubeapi-load-balancer    jujucharms   53  ubuntu  exposed
kubernetes-master        1.9.4    active      1  kubernetes-master        jujucharms   89  ubuntu  
kubernetes-worker        1.9.4    active      2  kubernetes-worker        jujucharms  106  ubuntu  exposed
prometheus                        active      1  prometheus               jujucharms    5  ubuntu  
prometheus-alertmanager           active      1  prometheus-alertmanager  jujucharms    2  ubuntu  

Unit                        Workload  Agent  Machine  Public address  Ports               Message
easyrsa/0*                  active    idle   0/lxd/0  172.17.30.74                        Certificate Authority connected.
etcd/0*                     active    idle   0        172.17.30.71    2379/tcp            Healthy with 1 known peer
grafana/0*                  active    idle   0/lxd/1  172.17.30.76    3000/tcp            Started grafana-server
kubeapi-load-balancer/0*    active    idle   0        172.17.30.71    443/tcp             Loadbalancer ready.
kubernetes-master/0*        active    idle   0        172.17.30.71    6443/tcp            Kubernetes master running.
  flannel/1                 active    idle            172.17.30.71                        Flannel subnet 10.1.28.1/24
kubernetes-worker/0         active    idle   1        172.17.30.72    80/tcp,443/tcp      Kubernetes worker running.
  flannel/2                 active    idle            172.17.30.72                        Flannel subnet 10.1.89.1/24
kubernetes-worker/1*        active    idle   2        172.17.30.73    80/tcp,443/tcp      Kubernetes worker running.
  flannel/0*                active    idle            172.17.30.73                        Flannel subnet 10.1.64.1/24
prometheus-alertmanager/0*  active    idle   0/lxd/3  172.17.30.75    9093/tcp            Ready
prometheus/0*               active    idle   0/lxd/2  172.17.30.77    9090/tcp,12321/tcp  Ready

Machine  State    DNS           Inst id              Series  AZ       Message
0        started  172.17.30.71  rdkykf               xenial  default  Deployed
0/lxd/0  started  172.17.30.74  juju-3f8b8d-0-lxd-0  xenial  default  Container started
0/lxd/1  started  172.17.30.76  juju-3f8b8d-0-lxd-1  xenial  default  Container started
0/lxd/2  started  172.17.30.77  juju-3f8b8d-0-lxd-2  xenial  default  Container started
0/lxd/3  started  172.17.30.75  juju-3f8b8d-0-lxd-3  xenial  default  Container started
1        started  172.17.30.72  cqpcs3               xenial  default  Deployed
2        started  172.17.30.73  mdc8es               xenial  default  Deployed

Relation provider                             Requirer                             Interface         Type         Message
easyrsa:client                                etcd:certificates                    tls-certificates  regular      
easyrsa:client                                kubeapi-load-balancer:certificates   tls-certificates  regular      
easyrsa:client                                kubernetes-master:certificates       tls-certificates  regular      
easyrsa:client                                kubernetes-worker:certificates       tls-certificates  regular      
etcd:cluster                                  etcd:cluster                         etcd              peer         
etcd:db                                       flannel:etcd                         etcd              regular      
etcd:db                                       kubernetes-master:etcd               etcd              regular      
kubeapi-load-balancer:loadbalancer            kubernetes-master:loadbalancer       public-address    regular      
kubeapi-load-balancer:website                 kubernetes-worker:kube-api-endpoint  http              regular      
kubernetes-master:cni                         flannel:cni                          kubernetes-cni    subordinate  
kubernetes-master:kube-api-endpoint           kubeapi-load-balancer:apiserver      http              regular      
kubernetes-master:kube-control                kubernetes-worker:kube-control       kube-control      regular      
kubernetes-worker:cni                         flannel:cni                          kubernetes-cni    subordinate  
prometheus-alertmanager:alertmanager-service  prometheus:alertmanager-service      http              regular      
prometheus:grafana-source                     grafana:grafana-source               grafana-source    regular      

NodeExporterのデプロイ

Prometheusが各ノードの情報を取得するためのNodeExporterを導入します。 今回はKubernetesのデーモンセットとして機能させます。 監視コンポーネントのように、ノードに対し一律で配置するようなものについては、Kubernetesデーモンセットを使用すると新規追加したノードにも自動的にコンポーネントが展開されます。

$ vim node-exporter.yml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: node-exporter
spec:
  template:
    metadata:
      labels:
        app: node-exporter
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '9100'
    spec:
      containers:
      - name: node-exporter
        image: prom/node-exporter:v0.13.0
        ports:
        - containerPort: 9100
      hostNetwork: true
      hostPID: true
$ kubectl create -f node-exporter.yml

Kubernetes-Metricsのデプロイ

Kubernetesクラスタを監視するメトリクスを追加します。

$ vim kubernetes-metrics.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kube-state-metrics
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: kube-state-metrics
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '8080'
    spec:
      containers:
      - name: kube-state-metrics
        image: gcr.io/google_containers/kube-state-metrics:v0.3.0
        ports:
        - containerPort: 8080
$ kubectl create -f kubernetes-metrics.yml

Prometheusジョブ設定

jujuからPrometheusの”scrape-jobs (string)”へ追記

- job_name: 'kubernetes-pods'

  kubernetes_sd_configs:
  - role: pod

  relabel_configs:
  - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
    action: keep
    regex: true
  - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
    action: replace
    target_label: __metrics_path__
    regex: (.+)
  - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
    action: replace
    regex: (.+):(?:\d+);(\d+)
    replacement: ${1}:${2}
    target_label: __address__
  - action: labelmap
    regex: __meta_kubernetes_pod_label_(.+)
  - source_labels: [__meta_kubernetes_namespace]
    action: replace
    target_label: kubernetes_namespace
  - source_labels: [__meta_kubernetes_pod_name]
    action: replace
    target_label: kubernetes_pod_name

f:id:virtualtech:20180329095124p:plain

Prometheusターゲット設定

jujuからPrometheusの”static-targets (string)”へ追記

172.17.30.72:4194,
172.17.30.73:4194,
172.17.30.72:9100,
172.17.30.73:9100

f:id:virtualtech:20180329095150p:plain

Prometheusデプロイ

Save Changes → Commit changes

Target確認

Prometheusデプロイ先アドレスにWebアクセスし、targetの状態を確認します。 StatusがUPとなっていれば成功です。 デプロイ先アドレスがわからなくなった場合はjuju statusを使いましょう。

http://172.17.30.77:9090/targets

Grafanaログイン情報の表示

Grafanaへログインするためのパスワードを表示します。

$ juju run-action --wait grafana/0 get-admin-password

unit-grafana-0:
  id: a39e3b5d-e975-4348-8479-9d511f52cf86
  results:
    password: password5656
  status: completed
  timing:
    completed: 2018-03-26 07:33:58 +0000 UTC
    enqueued: 2018-03-26 07:33:57 +0000 UTC
    started: 2018-03-26 07:33:58 +0000 UTC
  unit: grafana/0

Grafanaへ接続

Prometheusデプロイ先アドレスにWebアクセスします。 ユーザ名はadmin、パスワードは上記で表示されたものを使用します。

http://172.17.30.76:3000

ダッシュボード作成

Grafanaでダッシュボードを作成します。 importからIDを指定すると、Grafanaで公開されているテンプレートを使用できます。

f:id:virtualtech:20180329095226p:plain

ID:3119のテンプレートを使用したグラフが以下となります。

f:id:virtualtech:20180329095246p:plain

今回は性能監視についてKubernetesでの実現方法の一例を取り上げました。 基盤がどのようなものであれ、アプリケーション開発者、インフラ管理者双方が関心を寄せる事項かと思われます。

Jujuで構築するKubernetesでGPUを使うには

JujuでKubernetesをデプロイする方法については以前、次のように書きました。

tech.virtualtech.jp

では、今回は「Jujuで構築するKubernetesでGPUを使うには」と言う話を書きたいと思います。 と言っても割と簡単で、Kubernetes WorkerノードにNVIDIAのGPUを挿して、juju deploy kubernetesでドーンで終了です。

JujuでGPU対応Kubernetes環境を作る

前回の記事でも書いた通り、Jujuはアプリケーション単体の設定などが定義されているCharm、それを一つにまとめたBundleがあります。Kubernetes WorkerのCharmの設定一覧を見ると簡単に導入できる理由がわかると思います。

現在のKubernetes Workerのリビジョンは106が安定版なので、その設定は次のURLで確認できます(2018年3月28日現在)。

https://api.jujucharms.com/charmstore/v5/~containers/kubernetes-worker-106/archive/config.yaml

該当の部分を抜き出すとこれです。install-cudaのパラメーターがデフォルトでtrueになっています。このため、Kubernetes WorkerのホストにNVIDIAのGPUカードが接続されていると必要なGPUドライバーやCUDAなどがインストールされるわけです。

  "install-cuda":
    "type": "boolean"
    "default": !!bool "true"
    "description": |
      Install the CUDA binaries if capable hardware is present.

[平成30年5月24日 追記]

最近のJuju Charmでは、install-cudaというオプションは無くなっています。NVIDIA GPUがスロットに接続されている場合、デフォルトで必要なドライバーがホストにインストールされます。

さて、DockerコンテナーでGPUを使うと言うと「NVIDIA Docker」を想像すると思います。実はKubernetesでGPUを取り扱う場合、NVIDIA Dockerは必須ではありません。JujuでKubernetesの環境を作った場合はGPU DriverとCUDAがインストールされるだけで、NVIDIA Dockerは自動的にインストールはされません。アプリケーションの要件でNVIDIA Dockerが必要な場合はGPUが載ったKubernetes Workerノードにログインしてインストールする必要があります。juju sshコマンドでログインして、セットアップします。

github.com

[平成30年5月24日 追記]

最近のJuju Charmでは、GPUをコンテナーで利用するために必要なパッケージは自動でインストールされます。

GitHubサイトを確認すると、NVIDIA Dockerにはバージョン1と2が存在するのがわかります。バージョン1は開発終了しており、新しく導入するのであればバージョン2の導入をおススメします。

ところがQuick Startに従ってインストールしようとすると「docker-ceがインストールされていないので無理」といわれて詰みます。

% sudo apt-get install -y nvidia-docker2

JujuでKubernetes Workerを構築した場合、Ubuntuアーカイブのdocker.ioパッケージがインストールされます。nvidia-docker2をインストールするときに導入済み済みのDockerバージョンを指定するとうまくいきます。方法は次のページに書かれています。

github.com

まず、公開されているnvidia-docker2とnvidia-container-runtimeのバージョンを確認します。現在はnvidia-docker2はバージョン2.0.3、nvidia-container-runtimeは2.0.0が最新版のようです。

出力結果を見ればわかるように、Dockerのバージョンによって導入すべきパッケージが分かれていることがわかると思います。

% apt list -a nvidia-docker2 nvidia-container-runtime
Listing... Done
nvidia-container-runtime/xenial 2.0.0+docker18.03.0-1 amd64
nvidia-container-runtime/xenial 2.0.0+docker17.12.1-1 amd64
nvidia-container-runtime/xenial 2.0.0+docker17.12.0-1 amd64
nvidia-container-runtime/xenial 2.0.0+docker17.09.1-1 amd64
nvidia-container-runtime/xenial 2.0.0+docker17.09.0-1 amd64
nvidia-container-runtime/xenial 2.0.0+docker17.06.2-1 amd64
nvidia-container-runtime/xenial 2.0.0+docker17.03.2-1 amd64
nvidia-container-runtime/xenial 2.0.0+docker1.13.1-1 amd64
nvidia-container-runtime/xenial 2.0.0+docker1.12.6-1 amd64
...
nvidia-docker2/xenial 2.0.3+docker18.03.0-1 all
nvidia-docker2/xenial 2.0.3+docker17.12.1-1 all
nvidia-docker2/xenial 2.0.3+docker17.12.0-1 all
nvidia-docker2/xenial 2.0.3+docker17.09.1-1 all
nvidia-docker2/xenial 2.0.3+docker17.09.0-1 all
nvidia-docker2/xenial 2.0.3+docker17.06.2-1 all
nvidia-docker2/xenial 2.0.3+docker17.03.2-1 all
nvidia-docker2/xenial 2.0.3+docker1.13.1-1 all 
nvidia-docker2/xenial 2.0.3+docker1.12.6-1 all

Ubuntu 16.04.4でインストールされるdocker.ioは次のように実行すると1.13.1であることがわかります。

% apt list -a docker.io
Listing... Done
docker.io/xenial-updates,now 1.13.1-0ubuntu1~16.04.2 amd64 [installed]
docker.io/xenial 1.10.3-0ubuntu6 amd64

というわけで、次のようにバージョンを指定してインストールすれば良いわけです。

% sudo apt install -y nvidia-docker2=2.0.3+docker1.13.1-1 nvidia-container-runtime=2.0.0+docker1.13.1-1

ついでに関連するパッケージのバージョンを固定しておくと良いでしょう。

% sudo apt-mark hold docker.io nvidia-docker2 nvidia-container-runtime

GPU Podを作ろう

それでは早速、GPUが使えるPodを作ってみます。KubernetesのGPU Scheduleについては次のページにまとまっています。

kubernetes.io

ここに書かれているサンプルファイルをちょっと書き換えて、次のようなYAMLを書いてみます。この後の旧方式のYAMLと比べてシンプルであるのがわかると思います。ちなみにnvidia.com/gpuを使った場合はドライバーやライブラリーのパスの指定などは自動でやってくれるので、DockerのCUDAイメージを使わないで(例えばcentos:7とかubuntu:18.04などを指定して)も、PodにGPUを割り当てることができます。

apiVersion: v1
kind: Pod
metadata:
  name: testpod-cuda91
spec:
  restartPolicy: OnFailure
  containers:
    - name: testpod-cuda91
      image: nvidia/cuda:9.1-base
      tty: true
      resources:
        limits:
          nvidia.com/gpu: 1

なお、Kubernetes 1.9,1.10では、Kubernetes 1.6や1.7の頃のYAMLの書き方でもGPU Podを作ることができます。 GPUドライバーなどのパスを指定していたり、alpha.kubernetes.io/nvidia-gpuを指定していたりする点が異なります。

この形式はKubernetes 1.10までサポートされますが、いずれサポートを終了するとのことです。

apiVersion: v1
kind: Pod
metadata:
  name: testpod
spec:
  restartPolicy: OnFailure
  containers:
    - name: nvidia-cuda
      image: nvidia/cuda:9.1-base
      tty: true
      resources:
        limits:
          alpha.kubernetes.io/nvidia-gpu: 1 # requesting 1 GPU
      volumeMounts:
        - name: bin
          mountPath: /usr/local/nvidia/bin
        - name: lib 
          mountPath: /usr/local/nvidia/lib
  volumes:
      - hostPath:
          path: /usr/lib/nvidia-390/bin
        name: bin
      - hostPath:
          path: /usr/lib/nvidia-390
        name: lib

旧形式の記述ではvolumesでは、Kubernetes Workerホストにインストール済みのNVIDIAツールの実行ファイルと関連ライブラリーを指定しています(当然ながらバージョンを指定していますので、バージョンが変わればここの記述も変える必要があります。)。これを上のcontainersのvolumeMountsでマウントしています。

いずれかのYAMLをfirst-gpupod.ymlという名前で保存して、次のように実行しましょう。 GPU Podが作成できます。

% kubectl create -f first-gpupod.yml
% kubectl get pod testpod
NAME      READY     STATUS    RESTARTS   AGE
testpod   1/1       Running   0          2m

% kubectl exec -it testpod nvidia-smi
Wed Mar 28 01:33:02 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.30                 Driver Version: 390.30                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 1080    Off  | 00000000:01:00.0 Off |                  N/A |
| 27%   29C    P8    11W / 180W |      0MiB /  8119MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

作成したPodは次のコマンドで削除できます。

% kubectl delete -f first-gpupod.yml

こんな形です。本日は以上です。 次回はChainerでGPUを使ってなにかやってみたいと思います。

Jujuで構築するKubernetes

最近弊社ではKubernetesの検証を始めています。

Kubernetesは、展開、スケーリング、コンテナー上で動くアプリケーションの管理を自動化するために設計されたオープンソースのプラットフォームです。2014年にKubernetesプロジェクトがGoogle主導のもと始まりました。

Kubernetesには次のような特徴があります。

  • 疎結合である
  • 負荷に応じてスケールできる
  • ホストOSから抽象化したレイヤーでアプリケーションを実行できる
  • 迅速にアプリケーションを展開できる
  • その場でアプリケーションをスケールできる
  • シームレスに新機能をロールアウトできる
  • 必要なリソースだけにハードウェアの使用率を制限できる

Kubernetesは色々なOS上で動かすことができますが、今回はUbuntu ServerとJuju、MAASを使って、Kubernetes環境を構築してみました。

Jujuはいわゆるアプリケーションの構成管理ツールの一つです。アプリケーションの構成管理ツールというと、AnsibleとかPuppetとかが知られていると思います。それらとJujuの相違点として、OSのデプロイメントから行う点、接続関連の設定も行う点が挙げられます。使い方も比較的簡単なので、ドキュメントさえ見れば初めての人でも簡単に小規模から大規模のアプリケーションを構成することができます。

Jujuは色々なノードにデプロイメントできますが、今回はベアメタルサーバー上に環境を構築するためにMAASを利用します。次のドキュメントに従って、それぞれが準備できていることが前提です。

Jujuのドキュメント

jujucharms.com

MAASのドキュメント

https://docs.ubuntu.com/maas/2.3/en/docs.ubuntu.com

MAASの必須要件

MAASでは組み込みのDHCPが提供したいネットワークセグメント上で動いていることが要件です。また、JujuとJujuによってデプロイするマシンとの関連づけのためにMAASのタグ機能を使います。識別しやすいタグを設定しておきましょう。

Jujuの必須要件

当たり前のことを言って恐縮ですが、JujuとMAASの組み合わせで利用する場合はJujuがMAASサーバーとネットワークの疎通があることが要件です。JujuはbootstrapというプロセスをMAASで管理されているノードにデプロイする必要があります。Jujuを操作するクライアント(CLI、GUI)からの命令をbootstrap経由でほかのノードに対して実行するような仕組みです。

Jujuとデプロイメント

JujuにはCharmとBundleというものがあります。Charmはアプリケーションと設定ファイルが含まれたパッケージで、GUIやCLIでアプリケーション単体をデプロイするものです。

一方Bundleはアプリケーションをデプロイするためのマシンの確保からOSのデプロイメントとアップデート、アプリケーションのデプロイメント、アプリケーションの設定、サービスの再起動、アプリケーション間の接続のための設定などを自動で行うことができるパッケージです。CharmもBundleもYAMLファイルで独自の設定を含めてデプロイメントできます。

JujuでKubernetesをデプロイしよう

環境さえ用意できれば次のコマンドだけでサーバー9台のマシンにKubernetes環境をデプロイメントできます。

% juju deploy canonical-kubernetes

設定はこちらのページに掲載されているファイル一覧のbundle.yamlに記載されており、ここを書き換えることでユーザーの好みの構成で環境をデプロイメントできます。

jujucharms.com

以下はYAMLの記述例です。ちなみにCharmストアのパッケージを使う場合、CharmやBundleのデプロイに必要なのは実はbundle.yamlだけです。このファイルをwgetやcurlコマンドで手元に持ってきて、後は好きな構成に書き換えれば公式のマシン数以外でのデプロイが可能になります。

さて、BundleのYAMLファイルの記述方法ですが、machinesにはどのようなスペックのマシンを何台、この環境構築に用いるのかを指定します。 servicesにはアプリケーションを記述しています。num_unitsにはそのアプリケーションをどれだけスケールするかを指定します。2以上を指定した場合はHA構成のための設定が自動的にセットアップされます。num_unitsの下のtoでデプロイ先のJuju Machineを指定します。数字だけを記述するとベアメタル上にアプリケーションを配備して、lxd:Xのように記述するとLXCコンテナー上にアプリケーションをデプロイメントします。relationsでアプリケーション間の「繋がり」を定義します。

description: A 3-machine Kubernetes cluster, appropriate for PoC. Includes a 2 Kubernetes worker nodes.
machines:
  '0':
    series: xenial
    constraints: "arch=amd64 tags=bay10" 
  '1':
    series: xenial
    constraints: "arch=amd64 tags=bay11"
  '2':
    series: xenial
    constraints: "arch=amd64 tags=bay12"
services:
  easyrsa:
    annotations:
      gui-x: '450'
      gui-y: '550'
    charm: cs:~containers/easyrsa-23
    num_units: 1
    to:
    - lxd:0
  etcd:
    annotations:
      gui-x: '800'
      gui-y: '550'
    charm: cs:~containers/etcd-59
    num_units: 1
    to:
    - '0'
  flannel:
    annotations:
      gui-x: '450'
      gui-y: '750'
    charm: cs:~containers/flannel-35
  kubeapi-load-balancer:
    annotations:
      gui-x: '450'
      gui-y: '250'
    charm: cs:~containers/kubeapi-load-balancer-37
    expose: true
    num_units: 1
    to:
    - '0'
  kubernetes-master:
    annotations:
      gui-x: '800'
      gui-y: '850'
    charm: cs:~containers/kubernetes-master-65
    num_units: 1
    options:
      channel: 1.9/stable
    to:
    - '0'
  kubernetes-worker:
    annotations:
      gui-x: '100'
      gui-y: '850'
    charm: cs:~containers/kubernetes-worker-69
    expose: true
    num_units: 2
    options:
      channel: 1.9/stable
    to:
    - '1' 
    - '2'
relations:
- - kubernetes-master:kube-api-endpoint
  - kubeapi-load-balancer:apiserver
- - kubernetes-master:loadbalancer
  - kubeapi-load-balancer:loadbalancer
- - kubernetes-master:kube-control
  - kubernetes-worker:kube-control
- - kubernetes-master:certificates
  - easyrsa:client
- - etcd:certificates
  - easyrsa:client
- - kubernetes-master:etcd
  - etcd:db
- - kubernetes-worker:certificates
  - easyrsa:client
- - kubernetes-worker:kube-api-endpoint
  - kubeapi-load-balancer:website
- - kubeapi-load-balancer:certificates
  - easyrsa:client
- - flannel:etcd
  - etcd:db
- - flannel:cni
  - kubernetes-master:cni
- - flannel:cni
  - kubernetes-worker:cni

独自のコンフィグレーションが終わったら、次のように実行して後は待つだけでKubernetes環境が出来上がります。デプロイの状況はJuju GUIやJuju CLIのjuju statusコマンドで確認できます。

% juju deploy ./deploy.yaml

最小構成のシステムを構成するならざっと四、五十分程度でできるでしょうか。Jujuもほかの構成管理ツールと同様、冪等性があるツールです。同じBundleを使えば同じ構成でシステムのコピーを作れますし、作ったり壊したりも容易です。

今回細かくは触れませんが、JujuはModelというもので「アプリケーション軍」を管理しています。モデルで環境を分ければ、Jujuを使ってKubernetes環境やOpenStack環境、MySQLのクラスター環境などを一元管理できたりします。その辺りの話はまた今度取り上げたいと思います。

構築した環境はkubernetes-masterにログインしてkubectlコマンドなどで操作します。Jujuでデプロイした環境へはjuju sshコマンドでログインできますので、次のように操作してください。

% juju ssh kubernetes-master/0
ubuntu@bay10:~$ kubectl get nodes
NAME           STATUS    ROLES     AGE       VERSION
bay11          Ready     <none>    4d        v1.9.2
bay12          Ready     <none>    4d        v1.9.2

ただ、Kubernetes環境でデプロイするたびにログインするのも少し面倒です。次のように実行すると、必要なコマンドが手元の環境に導入されている前提で、Kubernetesの操作ができます。マスターノードからクレデンシャルファイルをjuju scpコマンドで持ってくれば良いわけです。ちなみに転送してきたファイルをホームディレクトリーの同じパスにおけば、色々なところのクライアントでKubernetesを操作できます。

% juju scp kubernetes-master/0:config ~/.kube/config
% kubectl get nodes
NAME           STATUS    ROLES     AGE       VERSION
bay11          Ready     <none>    4d        v1.9.2
bay12          Ready     <none>    4d        v1.9.2

と、こんな感じで割と簡単にKubernetesの環境を作ることができます。YAMLファイルにきちんと「構成したい構成」を書いておけば、後はJujuとbootstrapがその通りの環境を作ってくれるわけです。Juju、すごい。

ちなみにKubernetes関連のBundleは私が使い始めた頃よりも充実してきました。コミュニティによるBundleも含めると、16個ほど色々な構成をデプロイすることができるBundleが用意されています。

jujucharms.com

日本仮想化技術では従来の仮想化だけではなく、OpenStackやKubernetesについても力を入れていきたいと思っています。導入や検証検討の際は是非ご用命ください。