kubernetesによるDockerコンテナ管理入門

近年注目されているコンテナ技術「Docker」では、その盛り上がりとともにDockerと連携して利用するツールも多く登場している。今回はその1つであるDockerクラスタ管理ツール「kubernetes」について、その概要と基本的なクラスタ環境構築方法について解説する。

Dockerが提供する機能と提供しない機能

Dockerは「コンテナ型仮想化」と呼ばれる、OSの機能を使って隔離された環境(コンテナ)を使った仮想化技術だ。Dockerではコンテナの作成やコンテナ内でファイルシステムとして使われるイメージの作成および管理、コンテナの実行といった機能が提供されているが、ネットワークのルーティングや複数コンテナの連携、複数台のサーバーを対象にコンテナを横断的に管理する機能などは提供されていない。そのため、クラスタ環境でDockerを利用する場合は別途何らかの管理手法を用意する必要があった。

このようなDockerと連携して利用できるデプロイ/オーケストレーションツールの1つが、今回紹介するkubernetesだ。KuberneteはGoogleが立ち上げたプロジェクトで、MicrosoftやRed Hat、IBMなども開発に参加している(図1)。Googleはデータセンターですでにコンテナ技術を利用しており、その経験を生かして開発されているという。

図1 kubernetesのWebサイト

kubernetesが提供する機能

kubernetesでは、以下のような機能が提供される。

  • 関連するコンテナのグルーピング
  • コンテナに割り振られるIPアドレスの管理
  • コンテナ間のネットワークルーティング管理
  • 複数のコンテナを利用した負荷分散
  • コンテナに割り当てるストレージの管理
  • コンテナの監視

特に注目したいのがネットワーク関連の管理機能で、kubernetesを利用することで複数台のマシンから構成されるクラスタ環境において、どのマシン上でコンテナが稼動しているのかを意識せずにコンテナとIPアドレスを紐付けることが可能となる。

kubernetesのアーキテクチャ

kubernetesは表1の複数のコンポーネントから構成されている。

表1 kubernetesを構成するコンポーネント
コンポーネント名 説明
apiserver kubernetesを操作するためのAPIを提供する
controller-manager コンテナの状態管理やノードの管理と言った各種管理作業を行う
proxy コンテナへのネットワークルーティングおよび負荷分散を行う
scheduler 各ノードに対しコンテナの割り当てなどを行う
kubelet 各ノード上でのコンテナ作成/削除やボリュームの割り当てなどを行う
kubectl API経由でKubenetesを操作するためのクライアントツール

また、これらに加えてコンテナの実行やイメージの管理を行うためのDockerや、分散型設定共有サービス「etcd」も必要となる。そのほか、異なるマシン上で稼動しているコンテナ間で通信を行うためにLinuxのブリッジ接続機能や「Flannel」、「OpenVSwitch」といった仮想ネットワーク機構なども利用される。

これらのうち、apiserverやcontroller-manager、scheduler、etcdについてはクラスタの管理を行うマスターサーバーで実行されるコンポーネントとなる。また、proxyやkubelet、dockerはコンテナを稼動させる各ノード(minionとも呼ばれる)上で実行されている必要がある。

マスターサーバーとノードを分けた一般的な構成は、次の図2のようになる。なお、kubectlについてはマスターサーバー上でも、別のクライアント上でも実行が可能だ。

図2 kubernetesの基本的な構成

また、マスターサーバーとノードを1台のサーバー上で同時に動かすこともできる。このような構成は小規模な環境やテストなどで有用だ。

kubernetesを使ったクラスタ環境を構築する

それでは、実際にkubernetesを使ってクラスタ環境を構築する例を紹介していこう。今回構築する環境は図3のようなもので、1台のマスターサーバーと、複数台のコンテナ稼動ノードを用意した。すべてのマシンはローカルネットワークに接続されており、インターネットにはファイアウォール経由で接続されている。また、コンテナ間の通信を行うために仮想ネットワーク構築ツールであるflannelを利用している。

図3 今回作成するクラスタ環境

図2で例示されている仮想ネットワークについて補足しておくと、flannelによる仮想ネットワークはノードをまたいだコンテナ間通信を行うためのもので、これによってすべてのコンテナは同じ仮想ネットワーク(ここでは172.17.0.0/16)に接続されるようになる。

また、kube-proxyによる仮想ネットワークは、各コンテナで稼動しているサービスに対して一意なIPアドレスを割り当てるためのものだ。このネットワークにはkube-proxyが稼動しているホストからのみアクセスが可能となる。

各マシンのOSにはCentOS 7を利用し、事前に/etc/hostsファイルを用意して各マシンに対しホスト名でアクセスできるよう設定してある。また、各マシン上でのファイアウォールについてはすべて無効にしているが、実環境においては必要に応じて適切なファイアウォール設定を行うことを推奨する。

マスターサーバーでのkubernetesおよびetcd、flannelのインストールと設定

まずはマスターサーバーの構築だが、CentOS 7環境ではyumコマンドでetcdやkubernetes、flannelのインストールが可能だ。

# yum install etcd kubernetes flannel

パッケージをインストールしたら、続けてそれぞれの設定ファイルを環境に応じて修正し、サービスを立ち上げる作業を行う。

etcdの設定

kubernetesやflannelはその設定管理にetcdを使用するため、まずはetcdの設定を行ってサービスを起動しておく必要がある。etcdのデフォルト設定ではローカルホストからしかetcdにアクセスできないようになっているため、設定ファイルである/etc/etcd/etcd.confの「ETCD_LISTEN_CLIENT_URLS」項目を次のように修正する。

#ETCD_LISTEN_CLIENT_URLS="http://localhost:2379"
ETCD_LISTEN_CLIENT_URLS="http://master01:2379,http://localhost:2379"

修正が完了したら、etcdを起動しておく。

# systemctl start etcd

flannelの設定

次に、仮想ネットワークを利用するためのflannelの設定を行う。まず、etcdのコマンドラインクライアントであるetcdctlコマンドを使ってflannelが使用する仮想ネットワークのIPアドレスを指定しておく。

$ etcdctl mk /coreos.com/network/config '{"Network":"172.17.0.0/16"}'

(2017年6月12日追記)現在CentOS 7やFedoraで提供されているflannelパッケージでは、設定キーが「/coreos.com/network/config」から「/atomic.io/network/config」に変更されている。そのため、下記のように実行する必要がある。

$ etcdctl mk /atomic.io/network/config '{"Network":"172.17.0.0/16"}'

なお、この設定キーは/etc/sysconfig/flanneldファイル内の「FLANNEL_ETCD_PREFIX」というパラメータで変更可能だ。

続いてetcdと同様、systemctlコマンドでflannelを起動しておく。

# systemctl start flanneld

kubernetes関連の設定

kubernetesでは、/etc/kubernetes/ディレクトリ以下に各種設定ファイルがインストールされている。マスターサーバー側で変更が必要なのはapiserverファイルとconfigファイル、controller-managerファイルの3つだ。また、別途RSA鍵の用意も必要となる。

まず、opensslコマンドを使用してkubernetesのAPI認証に使用される鍵ファイルを/etc/kubernetes/serviceaccount.keyとして作成しておく。

# openssl genrsa -out /etc/kubernetes/serviceaccount.key 2048
Generating RSA private key, 2048 bit long modulus
....................+++
..........+++
e is 65537 (0x10001)

/etc/kubernetes/apiserverファイルでは以下のようにKUBE_API_ADDRESSパラメータの変更と、作成した鍵ファイルの指定を行う。これにより、他ノードがapiserverにアクセスできるようになる。

# The address on the local server to listen to.
#KUBE_API_ADDRESS="--address=127.0.0.1"
KUBE_API_ADDRESS="--address=192.168.1.50"  ←マスターサーバーのIPアドレスを指定する
# Add your own!
KUBE_API_ARGS="--service_account_key_file=/etc/kubernetes/serviceaccount.key"

/etc/kubernetes/controller-managerファイルでも同様に鍵ファイルの指定を行う。

# Add your own!
KUBE_CONTROLLER_MANAGER_ARGS="--service_account_private_key_file=/etc/kubernetes/serviceaccount.key"

/etc/kubernetes/configファイルではkubernetesのマスターサーバーを指定する項目があるので、これを次のように変更する。

# How the controller-manager, scheduler, and proxy find the apiserver
#KUBE_MASTER="--master=http://127.0.0.1:8080"
KUBE_MASTER="--master=http://master01:8080"

以上の設定が完了したら、各コンポーネントを起動させておく。

# systemctl start kube-apiserver
# systemctl start kube-controller-manager
# systemctl start kube-scheduler
# systemctl start kube-proxy

「kubectl」コマンドを利用するための設定

kubernetesでは、コマンドラインで各種操作を行うためのクライアント「kubectl」が用意されている。kubectlコマンドは設定ファイルとして~/.kube.configファイルを参照するため、事前にこのファイルを作成しておく必要がある。手作業で作成しても良いが、kubectlコマンドのconfigサブコマンドを使用することでこの設定ファイルを作成することも可能だ。具体的には、次のような手順となる。なお、この設定ファイルはユーザーごとに作成されるため、これらのコマンドはrootではなく通常kubernetesの操作を行うユーザーで実行する。

$ kubectl config set-credentials myself --username=admin --password=<適当なパスワード>
$ kubectl config set-cluster local-server --server=http://master01:8080
$ kubectl config set-context default-context --cluster=local-server --user=myself
$ kubectl config use-context default-context
$ kubectl config set contexts.default-context.namespace default

これにより設定ファイルが作成され、、マスターサーバー上からkubectlコマンドでkubernetesクラスタを操作できるようになる。設定が正しく完了していれば、次のように「kubectl cluster-info」コマンドでクラスタ情報を確認できるはずだ。

$ kubectl cluster-info
Kubernetes master is running at http://localhost:8080

(2018年5月22日追記)SSL/TLS証明書の設定

Kubernetesクラスタ内からKubernetesのapiserverへのアクセスが必要な場合、Kubernetesのドキュメントに記載された方法に従って、次のようにアクセスに必要なSSL/TLS証明書関連ファイルを作成しておく必要がある。これらのファイルは、/etc/kubernretes/caディレクトリ内に配置しておく。デフォルトではこのディレクトリは存在しないので、次のようにして作成しておく。また、以下の作業は作成した/etc/kubernetes/caディレクトリ内で行う。

$ mkdir -p /etc/kubernetes/ca
$ cd /etc/kubernetes/ca

今回作成する証明書は独自に発行するものであるため、まず独自の認証局(CA)証明書を作成する。

$ openssl genrsa -out ca.key 2048
$ openssl req -x509 -new -nodes -key ca.key -subj "/CN=<KubernetesマスターノードのIPアドレス>" -days 10000 -out ca.crt

これで、ca.keyおよびca.crtというCA証明書ファイルが作成される。次にサーバー鍵を作成する。

$ openssl genrsa -out server.key 2048

続いて、証明書を作成するための設定ファイルを「csr.conf」という名前で作成する。このファイルには次のような内容を記述する。

[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[ dn ]
C = JP
ST = <証明書作成者住所の都道府県名>
L = <証明書作成者住所の市町村名>
O = <証明書作成者の名称>
OU = <証明書作成の部署名
CN = <KubernetesマスターノードのIPアドレスまたはホスト名>

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster
DNS.5 = kubernetes.default.svc.cluster.local
IP.1 = <KubernetesマスターノードのIPアドレス>
IP.2 = <KubernetesサービスのIPアドレス>

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=@alt_names

ここで、「KubernetesサービスIPアドレス」はKubernetesが自動的に作成する「Kubernetes」Serviceに割り当てられたIPアドレスだ。これは次のようにして確認できる。

$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.254.0.1   <none>        443/TCP   5d

csr.confファイルを作成したら、次のようにしてサーバー証明書の署名リクエストファイルを作成する。

$ openssl req -new -key server.key -out server.csr -config csr.conf

最後にこれらファイルを使ってサーバー証明書を作成する。

$ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 10000 \
-extensions v3_ext -extfile csr.conf

以上で証明書の作成は完了だ。あとはこれらファイルの情報を設定ファイルに追加すれば良い。まず/etc/kubernetes/apiserverファイル内の「KUBE_API_ARGS」行を次のように変更する。ここでは、「--client-ca-file」および「--tls-cert-file」、「--tls-private-key-file」パラメータを追加している。

KUBE_API_ARGS="--client-ca-file=/etc/kubernetes/ca/ca.crt \
--tls-cert-file=/etc/kubernetes/ca/server.crt \
--tls-private-key-file=/etc/kubernetes/ca/server.key \
--service-account-key-file=/etc/kubernetes/serviceaccount.key"

また、/etc/kubernetes/controller-managerファイル内の「KUBE_CONTROLLER_MANAGER_ARGS」行を次のように変更する。ここでは、「--root-ca-file」および「--cluster-signing-cert-file」パラメータを追加している。

KUBE_CONTROLLER_MANAGER_ARGS="--service-account-private-key-file=/etc/kubernetes/serviceaccount.key \
--root-ca-file=/etc/kubernetes/ca/ca.crt \
--cluster-signing-cert-file=/etc/kubernetes/ca/ca.crt"

コンテナ稼動ノードの設定

続いてコンテナを稼動させるノードの設定を行う。コンテナ稼動ノードではetcdは不要で、dockerおよびkubernetes、flannelパッケージのみをインストールすれば良い。

# yum install docker kubernetes flannel

flannelの設定

flannelについては、デフォルトではローカルホスト上のetcdを参照しようとするため、設定ファイルを編集してマスターサーバー上のetcdを参照するよう変更する。

flannelの設定ファイルは/etc/sysconfig/flanneldだ。このファイル内の「FLANNEL_ETCD」部分を、次のようにマスターサーバーを参照するよう変更する。

# etcd url location.  Point this to the server where etcd runs
#FLANNEL_ETCD="http://127.0.0.1:2379"
FLANNEL_ETCD="http://master01:2379"

(2017年6月12日追記)現在CentOS 7やFedoraで提供されているflannelパッケージでは、「FLANNEL_ETCD」パラメータではなく「FLANNEL_ETCD_ENDPOINTS」パラメータで参照するマスターサーバーを指定するようになっている。そのため、次のように変更を行う必要がある。

# etcd url location.  Point this to the server where etcd runs
#FLANNEL_ETCD="http://127.0.0.1:2379"
FLANNEL_ETCD_ENDPOINTS="http://master01:2379"

変更後、flannelおよびdockerを起動しておく。

# systemctl start flanneld
# systemctl start docker

なお、flannelによるコンテナ共有設定を行う以前にdockerサービスを起動していた場合、Dockerが使用するネットワークブリッジデバイスに適切なIPアドレスが割り当てられず、dockerサービスの再起動が行えない場合がある。その場合、次のようにbrctlコマンドを使ってブリッジデバイスを一度削除すれば良い。なお、この作業にはbridge-utilsパッケージにて提供されているbrctlコマンドが必要なので、このパッケージをあらかじめインストールしておく必要がある。

# yum install bridge-utils  ←brctlコマンドをインストール
# systemctl stop docker  ←dockerサービスを一度停止させる
# ip link set dev docker0 down  ←dockerが使用する「docker0」ブリッジデバイスを停止させる
# brctl delbr docker0  ←「docker0」ブリッジデバイスを削除する
# systemctl start docker  ←dockerサービスを起動させる

kubernetes関連の設定

コンテナ稼動ノードでは、/etc/kubernetes/configおよび/etc/kubernetes/kubeletファイルの修正が必要だ。まず/etc/kubernetes/configファイルでは、「KUBE_MASTER」パラメータでマスターサーバーのIPアドレスを指定する。

# How the controller-manager, scheduler, and proxy find the apiserver
#KUBE_MASTER="--master=http://127.0.0.1:8080"
KUBE_MASTER="--master=http://master01:8080"

また、/etc/kubernetes/kubeletファイルでは、以下の3か所を修正する。まず、ノードのIPアドレスを「KUBELET_ADDRESS」パラメータで指定する。

# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)
#KUBELET_ADDRESS="--address=127.0.0.1"
KUBELET_ADDRESS="--address=192.168.1.51"  ←そのノードのIPアドレスを指定

「KUBELET_HOSTNAME」パラメータではそのノードのホスト名を指定する。これは空欄にしておくことで、そのホストで設定されているhostnameが利用される。

# You may leave this blank to use the actual hostname
#KUBELET_HOSTNAME="--hostname_override=127.0.0.1"
KUBELET_HOSTNAME="--hostname_override="

「KUBELET_API_SERVER」ではapiserverが稼動しているホストを指定する。

# location of the api-server
#KUBELET_API_SERVER="--api_servers=http://127.0.0.1:8080"
KUBELET_API_SERVER="--api_servers=http://master01:8080"

最後にkube-proxyおよびkubeletサービスを立ち上げれば設定は完了だ。

# systemctl start kube-proxy
# systemctl start kubelet

マスターサーバーおよびノードが正しく稼動しているかどうかは、マスターサーバー上で次のようにして「kubectl get nodes」コマンドを実行することで確認できる。無事ノードが稼動していれば、ホスト名とともに「Ready」というステータスが表示される。

$ kubectl get nodes
NAME      LABELS                          STATUS
node01    kubernetes.io/hostname=node01   Ready

以上でkubernetesノード上での設定は完了だ。この作業をすべてのノード上で行っておこう。

Kuberneteクラスタ上でのコンテナ管理

さて、クラスタの構築が完了したら、実際にKuberneteクラスタ上でkubectlコマンドを使ってコンテナの作成や各種操作を行ってみよう。

kubernetesでは、コンテナを「Pod」という単位で管理するようになっている。そのため、コンテナを実行するにはまずPodを作成する必要がある。

kubectlコマンドでPodを作成するには、作成するPodの情報を記述したYAML形式ファイルを用意し、それを「kubectl create」コマンドの引数として与えれば良い。たとえば、「httpd」(DockerHubで公開されている、Docker公式のApache HTTP Serverを含んだコンテナイメージ)というイメージからコンテナを作成し、そのコンテナの80番ポートに外部からアクセスできるようにするには、以下のような設定ファイル(「pod-httpd.yaml」)を用意する。

apiVersion: v1
kind: Pod  ←Podに関する設定ファイルであることを指定
metadata:  ←メタデータに関する情報を指定
  name: httpd  ←Podの名前を指定
  labels:  ←Podに付与するラベルを指定
    app: httpd
spec:
  containers:
  - name: httpd  ←コンテナ名を指定
    image: httpd  ←コンテナを作成する際に使用するイメージを指定
    ports:
    - containerPort: 80  ←コンテナに外部からアクセスできるポートを指定

設定ファイルにはコンテナに関する情報のほか、さまざまなメタデータを記述することが可能だ。「metadata:」以下の項目がメタデータを指定するもので、まず「name:」ではPodの名前を指定している。また、「labels:」以下ではこのPodに付与する「ラベル」を指定している。ラベルは後述するServiceなどでPodを特定する際に使われるもので、任意のキーと値の組み合わせを使用可能だ。ここでは「app: httpd」というラベルを指定している。

このファイルからPodを作成するには、次のように「-f」引数付きで「kubectl create」コマンドを実行する。

$ kubectl create -f pod-httpd.yaml
pods/httpd

Podが作成されると、自動的に指定したコンテナが作成される。Podの稼働状況は「kubectl get pods」コマンドで確認できる。

$ kubectl get pods
NAME      READY     STATUS    RESTARTS   AGE
httpd     0/1       Pending   0          6s

稼働中のPodの詳しい状況を確認するには、「kubectl get pod <Pod名>」コマンドを利用する。このコマンドでは「-o」オプションで出力形式を選択でき、たとえば「yaml」を指定するとYAML形式で各種情報が出力される。

$ kubectl get pod httpd -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: 2015-10-19T10:52:27Z
  labels:
    app: httpd
  name: httpd
  namespace: default
  resourceVersion: "199"
  selfLink: /api/v1/namespaces/default/pods/httpd
  uid: 7d251bdf-764f-11e5-852c-9ca3ba2aa4cc
spec:
  containers:
  - image: httpd
    imagePullPolicy: IfNotPresent
    name: httpd
    ports:
    - containerPort: 80
      protocol: TCP
    resources: {}
    terminationMessagePath: /dev/termination-log
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-rogit
      readOnly: true
  dnsPolicy: ClusterFirst
  nodeName: node01
  restartPolicy: Always
  serviceAccount: default
  serviceAccountName: default
  volumes:
  - name: default-token-rogit
    secret:
      secretName: default-token-rogit
status:
  conditions:
  - status: "True"
    type: Ready
  containerStatuses:
  - containerID: docker://0d86a3502c31cdce0707de471d65a97309befcfa69ba3b2cd34837312af2e405
    image: httpd
    imageID: docker://81c42bcdc4ccc200adbbb0dfaa3890700f0b0aaa579141340ff90614ac9482f4
    lastState: {}
    name: httpd
    ready: true
    restartCount: 0
    state:
      running:
        startedAt: 2015-10-19T10:52:28Z
  hostIP: 192.168.1.51
  phase: Running
  podIP: 172.17.97.2
  startTime: 2015-10-19T10:52:27Z

ここで「hostIP」がそのPodを動かしているホスト、「podIP」がそのPodに割り当てられたIPアドレスだ。この例の場合、「192.168.1.51」(node01)上でPodが稼動しており、Podには「172.17.97.2」というIPアドレスが割り当てられている。

この状態でPodを動かしているノード(node01)上で「docker ps」コマンドを実行してみると、「httpd」イメージからコンテナが作成されて稼動していることが分かる。

# docker ps
CONTAINER ID        IMAGE                                  COMMAND              CREATED             STATUS              PORTS               NAMES
0d86a3502c31        httpd                                  "httpd-foreground"   3 minutes ago       Up 3 minutes                            k8s_httpd.f29c170e_httpd_default_7d251bdf-764f-11e5-852c-9ca3ba2aa4cc_8efa094e
1bb021abccf3        gcr.io/google_containers/pause:0.8.0   "/pause"             3 minutes ago       Up 3 minutes                            k8s_POD.ef28e851_httpd_default_7d251bdf-764f-11e5-852c-9ca3ba2aa4cc_f61b02c5

また、Podに割り当てられたIPアドレス(172.17.97.2)にcurlなどのHTTPクライアントでアクセスすることで、Webブラウザが稼動していることが確認できる。

$ curl 172.17.97.2
<html><body><h1>It works!</h1></body></html>

Podを削除するには「kubectl delete pod <Pod名>」コマンドを使用する。

$ kubectl delete pod httpd
pods/httpd

永続ストレージを利用する

Dockerでは、コンテナ内のファイルシステムに加えた変更点は永続化されず、コンテナを削除すると実行前の状態に戻ってしまう。これはデータベースなどをコンテナ内で実行させる場合に問題となる。そのため、Podではコンテナに永続ストレージを割り当てる「Volumes」という機能が用意されている。

詳しくは公式ドキュメントのVolumesの項を参照して欲しいが、永続ストレージはコンテナ外のストレージをコンテナ内の指定したディレクトリにマウントすることで実現される。使用できるストレージとしてはノードのローカルファイルシステムやGoogle Compute Engine、Amazon Web Serviceなどのクラウドストレージ、NFS、iSCSIなどから選択可能だ。

たとえば、以下のYAMLファイルはMySQLを稼動させるためのPodの設定を記したものだ(pod-mysql.yaml)。ここでは、ノードのローカルファイルシステム内にある/var/kube-test/mysql-dbディレクトリをコンテナ内の/var/lib/mysqlにマウントするよう指定している。

apiVersion: v1
kind: Pod
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  volumes:
  - name: mysql-storage  ←ボリューム名を指定
    hostPath:
      path: /var/kube-test/mysql-db  ←ボリュームとして使用するローカルファイルシステムのパスを指定
  containers:
  - name: mysql
    image: mysql
    volumeMounts:
    - name: mysql-storage  ←コンテナにマウントするボリューム名を指定
      mountPath: /var/lib/mysql  ←マウント先を指定
    ports:
    - containerPort: 3306
    env:  ←環境変数を指定
    - name: MYSQL_ROOT_PASSWORD
      value: <MySQLのrootパスワード>

なお、ここではコンテナのイメージとしてDockerが公式で用意しているMySQLコンテナイメージを使用している。このコンテナイメージでは環境変数で各種設定が行えるようになっており、ここでは環境変数を指定する「env」項目でMySQLのrootパスワードを指定できる「MYSQL_ROOT_PASSWORD」という環境変数に値を指定している。

ちなみに、このようにローカルファイルシステムを永続ストレージとして指定した場合、Podが実行されるノードが変わるとストレージ上の内容も変わってしまう点には注意したい。実行されるノードが変わっても同じデータを参照したい場合はNFSやiSCSIなどを利用するか、もしくはクラウドストレージを利用することになる。

Podに特定のIPアドレスを割り当てる

kubernetesでは、どのPodがどのノードで実行されるかはPodの作成時に決定される。つまり、Podが作成されるまではそれぞれのPodにどのIPアドレスが紐付けられるかは分からない。そのため、指定したPodに特定のIPアドレスを割り当てるための「Service」という仕組みが用意されている。

この機能を利用するには、まずPodと場合と同様にYAML形式の設定ファイルを用意する。たとえば、「mysql-master」というServiceを作成し、先ほど作成した「mysql」Podをその提供元として登録するには、次のような設定ファイル(service-mysql.yaml)を用意する。

apiVersion: v1
kind: Service  ←Serviceの定義であることを指定
metadata:
  name: mysql-master  ←Service名を指定
spec:
  ports:
    - port: 3306  ←Serviceが使用するポート番号を指定
  selector:  ←対象とするPodをラベルで指定
    app: mysql

Serviceに対して登録するPodはラベルを使って指定する。具体的には、「spec」以下の「selector」項目でラベルを指定することで、そのラベルに対応するPodがServicesとして登録される。今回の例では、「app: mysql」というラベルが付けられたPodがServiceに登録される。

作成した設定ファイルを引数として指定して「kubectl create」コマンドを実行するとServiceが作成される。

$ kubectl create -f service-mysql.yaml
services/mysql-service

作成されたサービス一覧は「kubectl get services」コマンドで確認できる。

$ kubectl get services
NAME           LABELS                                    SELECTOR    IP(S)           PORT(S)
kubernetes     component=apiserver,provider=kubernetes   <none>      10.254.0.1      443/TCP
mysql-master   <none>                                    app=mysql   10.254.31.188   3306/TCP

この結果を見ると、「mysql-master」というServiceには「10.254.31.188というIPアドレスが割り当てられていることが分かる。こ のIPアドレスへの接続は紐付けられたPodにルーティングされるようになっており、これを利用することでどのノード上でPodが動いているかを意識せずにサービスにアクセスできる。たとえば今回の例では、以下のようにしてPod上で動いているMySQLサーバーにアクセスできる。

$ mysql -h 10.254.31.188 -u root -p

別のPodにアクセスするようなPodを作成する

Serviceを作成すると、各コンテナ内ではそれに応じた環境変数が作成される。たとえば「mysql-master」というServiceの場合、そのServiceにアクセスするためのIPアドレスが「MYSQL_MASTER_SERVICE_HOST」という環境変数に、Serviceが使用するポートが「MYSQL_MASTER_SERVICE_PORT」という環境変数に格納される。たとえばMySQLデータベースを使用するアプリケーションを稼動させるPodでは、これらの環境変数を使って接続先データベースのホストを指定するようにすれば良い。

ここでは例として、WordPressが稼動するPodの作成手順を紹介しよう。まず、WordPressが含まれるDockerイメージを作成するための以下のようなDockerfileを用意する。

FROM php:5.6-apache

RUN a2enmod rewrite

RUN apt-get update && apt-get install -y libpng12-dev libjpeg-dev && rm -rf /var/lib/apt/lists/* \
        && docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \
        && docker-php-ext-install gd
RUN docker-php-ext-install mysqli

COPY html/ /var/www/html/

このDockerfileはPHP 5.6とApache HTTP Serverが含まれた「php:5.6-apache」イメージをベースに、WordPressの実行に必要なパッケージをインストールし、さらにDockerfileと同じディレクトリ内にあるhtml/ディレクトリの中身をイメージ内の/var/www/html/ディレクトリにコピーするものだ。

また、WordPressのWebサイトからWordPressのアーカイブをダウンロードし、次のようにしてhtml/ディレクトリ内にその中身を展開しておく。

$ tar xvzf wordpress-4.3.1-ja.tar.gz --transform=s/^wordpress/html/

続いてWordPressの設定ファイルのテンプレートであるwp-config-sample.phpファイルを「wp-config.php」ファイルとしてコピーし、データベース関連の設定項目が記述されている部分を次のように編集する。

cp html/wp-config-sample.php html/wp-config.php
/** WordPress のためのデータベース名 */
define('DB_NAME', 'wordpress');

/** MySQL データベースのユーザー名 */
define('DB_USER', 'wordpress');

/** MySQL データベースのパスワード */
define('DB_PASSWORD', '<データベースのパスワード>');

/** MySQL のホスト名 */
if (isset($_ENV["MYSQL_MASTER_SERVICE_HOST"]))
    define('DB_HOST', $_ENV["MYSQL_MASTER_SERVICE_HOST"]);
else
    define('DB_HOST', 'localhost');

ここでは使用するデータベース名およびユーザー名としてともに「wordpress」を指定している。また、データベースのホスト名は先に作成したmysql-masterサービスに対応する「MYSQL_MASTER_SERVICE_HOST」環境変数から取得するよう設定している。事前にこのデータベースにmysqlコマンドなどでアクセスし、使用するユーザーやデータベースを作成しておこう。

最後に「docker build」コマンドを実行し、Dockerイメージを作成する。ここでは作成したイメージに「hylom/wordpress」というタグを付けておいた。

# docker build -t hylom/wordpress .

Dockerプライベートリポジトリを使う

kubernetesでは、コンテナ関連の処理はすべてDockerに任せる構造になっており、Dockerイメージの管理を行う機能は用意されていない。そのため、独自のDockerイメージを利用する場合には各ノードにあらかじめ使用したいDockerイメージを配置しておくか、もしくは各ノードからアクセスできるDockerリポジトリ上にDockerイメージを登録しておく必要がある。

Dockerリポジトリを提供するDockerHubというサービスもあるが、一般公開されないプライベートリポジトリの作成には制限がある。そのたため、今回はローカルネットワーク内にプライベートリポジトリを作成して利用する方法を紹介しよう。なお、DockerHubについては以前に紹介しているので、そちらも参照してほしい(Dockerコンテナをクラウドサービス上で共有できる「Docker Hub」を使ってみる)。

Dockerのプライベートリポジトリを作成するには「Docker Regstry」というソフトウェアを利用する。CentOS 7では「docker-registry」というパッケージで提供されており、これをインストールするだけでプライベートリポジトリが利用可能になる。今回はマスターサーバー(master01)上にこのパッケージをインストールし、プライベートリポジトリの機能を提供させることにする。

# yum install docker-registry

(2017年6月15日追記)docker-registryパッケージは現在ではすでに廃止となっており、代わりに 「docker-distribution」というパッケージで同様の機能が利用できるようになっている。このパッケージを利用するには、次のようにしてインストールを行えば良い。

# yum install docker-distribution

なお、デフォルトではローカルホストからのみプライベートリポジトリにアクセスできるようになっているので、インストール後に設定ファイルである/etc/sysconfig/docker-registryファイルの下記の個所を編集し、待ち受けを行うIPアドレスを指定しておこう

# Address to bind the registry to
#REGISTRY_ADDRESS=0.0.0.0
REGISTRY_ADDRESS=192.168.1.50  ←待ち受けを行うIPアドレスを指定する

設定ファイルを編集したら、systemctlコマンドでサービスを起動させる。

# systemctl start docker-registry

(2017年6月15日追記)docker-distributionパッケージの場合、設定は/etc/docker-distribution/registry/config.ymlファイルで行う。ここでは、次のように「addr:」行に待ち受けを行うIPアドレスを追加すれば良い。サービスの起動は「systemctl start docker-distribution」コマンドで実行可能だ。

version: 0.1
log:
 fields:
 service: registry
storage:
 cache:
 layerinfo: inmemory
 filesystem:
 rootdirectory: /var/lib/registry
http:
 addr:192.168.1.50:5000 ←待ち受けを行うIPアドレスを指定する
# systemctl start docker-distribution

また、このリポジトリにアクセスする各ホストでdockerの設定ファイルである/etc/sysconfig/dockerファイルを編集し、次の1行を追加しておく。

INSECURE_REGISTRY='--insecure-registry 192.168.1.50:5000'

以上で設定作業は完了だ。続いて作成したイメージファイルをプライベートリポジトリにアップロード(push)するには、まず対象のイメージに「<リポジトリのIPアドレスもしくはFQDN>:<ポート>/<イメージ名>」というタグを設定する。

たとえば先ほど作成した「hylom/wordpress」というイメージを192.168.1.50というホスト上にあるプライベートリポジトリにpushしたい場合、次のようになる。

# docker tag hylom/wordpress 192.168.1.50:5000/wordpress

続いて、「docker push」コマンドでこのイメージをpushする。

# docker push 192.168.1.50:5000/wordpress
The push refers to a repository [192.168.1.50:5000/wordpress] (len: 1)
Sending image list
Pushing repository 192.168.1.50:5000/wordpress (1 tags)
e2a4fb18da48: Image successfully pushed 
  
  
Pushing tag for rev [b2548be77cbb] on {http://192.168.1.50:5000/v1/repositories/wordpress/tags/latest}

以上で、「192.168.1.50:5000/wordpress」というDockerイメージが利用可能になる。次のようなPodの設定ファイル(pod-wordpress.yaml)を用意すれば、このイメージを元にコンテナの作成が行える。

apiVersion: v1
kind: Pod
metadata:
  name: wordpress
spec:
  containers:
  - name: wordpress
    image: 192.168.1.50:5000/wordpress
    ports:
    - containerPort: 80
      hostPort: 80

大規模な環境から小規模な環境まで便利なkubernetes

以上、kubernetesを使ってDockerクラスタを構築する方法を紹介したが、今回の例のようにkubernetesを利用することで簡単に複数のコンテナを配備したり、ネットワーク越しに連携させることが可能になる。今回は紹介していないが、負荷分散や死活監視といった機能用意されており、大規模なクラスタ環境でも対応が可能だ。

また、kubernetesが提供するネットワーク管理機能は1台のマシン上に複数台のコンテナを稼動させるような小規模な環境においても便利だ。Dockerを利用した環境構築を検討しているなら、ぜひ試してみてはいかがだろうか。