新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。もう新人じゃないかも…

学習記録:Docker

第4章 Dockerの内部構造と関連ツール

Kubernetesによるオーケストレーション

Kubernetesのアーキテクチャ

Kubernetesの環境を構成するサーバ群は,以下のようになる。
Dockerが導入されたサーバはノードと呼ばれ,これらの上でコンテナが稼働する。コンテナを起動するための指示はKubernetesマスタに対して行う。このマスタサーバにログインし,kubectlコマンドで環境を操作することになる。また,外部のクライアントからマスタサーバのAPIに対してリクエストを送信することもできる。マスタサーバはそれぞれのノードのリソース使用状況を確認して,コンテナを起動するノードを自動的に選択する。
ほかには,環境の構成情報を保存するDBとしてetcdが動作するサーバ,必要であればプライベートレジストリがある。コンテナを起動するためのイメージは,Docker Hubもしくはこのプライベートレジストリから取得する。

Kubernetesの環境では,コンテナとのネットワーク通信の経路が少し複雑になる。前節で説明したように,各ノードの仮想ブリッジdocker0はコンテナ通信用の仮想ネットワークで相互接続されている。また,それぞれのノードのホストLinuxは物理ネットワークのIPアドレスを持っており,外部ネットワークからアクセスすることができる。このとき,各ノードではProxyサービスが稼働しており,外部ネットワークからのアクセスをコンテナに転送する役割を担う。

具体的に説明すると,外部ネットワークからコンテナ内のアプリケーションに接続する際には,事前に接続用のポート番号をサービスとして定義しておく。すると,Proxyサービスは指定のポート番号宛のパケットをコンテナ通信用ネットワークを経由してアプリケーションが稼働するコンテナに転送するようになる。つまり,外部ネットワークからは接続用のポート番号を指定して任意のノードに接続することで,アプリケーションを利用することができる。

さらに,このProxyサービスはLBの機能も提供する。Kubernetesでは同じコンテナイメージから複数コンテナをまとめて起動することができ,Webサーバのコンテナを複数起動しておくと,Proxyサービスはそれぞれのコンテナにアクセスを負荷分散してくれる。起動するコンテナの数を後から増減することもできるので,外部からのアクセス量に応じてコンテナ数を増やすなどの対応も可能である。また,何らかの原因でコンテナが異常停止した場合,Kubernetesはそれを自動的に検知してコンテナを再起動してくれる。ノードそのものが停止した場合は,正常に稼働しているノードを選んでコンテナを起動する。

Kubernetesのインストール

ここでは,Kubernetesマスタ・etcd・プライベートレジストリの機能をまとめて提供するマスタサーバと,2台のノードからなる最小構成を構築する。

まず,各サーバについてCentOS7をインストールし,各サーバへのIPアドレスを名前解決できるように/etc/hostsに以下の内容を追加しておく。また,最新のパッケージにアップデート後firewalldサービスを無効化してサーバを再起動しておく。

<マスタサーバのIPアドレス> master01
<ノード1のIPアドレス> node01
<ノード2のIPアドレス> node02

続いて,マスタサーバを構築する。
まずはプライベートレジストリに必要なRPMパッケージを導入する。

# yum -y install epel-release
# yum -y install docker docker-registry python-sqlalchemy

その後,各設定ファイルを次のように編集する。

#  /etc/sysconfig/docker-registryの末尾に追加
SEARCH_BACKEND=sqlalchemy

#  /etc/docker-registry.yml の以下の行を編集
sqlalchemy_index_database: _env:SQLALCHEMY_INDEX_DATABASE:sqlite:////var/lib/docker-registry/docker-registry.db

#  /etc/sysconfig/dockerの末尾に追加
INSECURE_REGISTRY='--insecure-registry master01:5000'

設定ファイルの編集が完了したら,各サービスを起動する。

# mkdir /var/lib/docker-registry
# systemctl enable docker-registry.service
# systemctl start docker-registry.service
# systemctl enable docker.service
# systemctl start docker.service

次に,バックエンドDBのetcdとKubernetesマスタとしての機能を構成する。まず必要なRPMを導入。

# yum -y install etcd-2.0.11-2.el7 kubernetes-master-0.17.1-4.el7

ここで各設定ファイルを次のように編集。

#  /etc/etcd/etcd.confの次の行を編集
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:4001"

# /etc/kubernetes/configの次の行を編集
KUBE_MASTER="--master=http://master01:8080"

# /etc/kubernetes/apiserverの次の行を編集
KUBE_API_ADDRESS="--address=0.0.0.0"
KUBE_ETCD_SERVERS="--etcd_servers=http://master01:4001"

# /etc/kubernetes/controller-managerの次の行を編集
KUBELET_ADDRESSES="--machines=node01,node02"

その後etcd, kube-apiserver, kube-controller-manager, kube-schedulerの各サービスをenable/startしてマスタサーバの構成は完了になる。curl -s -L http://master01:8080/versionコマンドでAPIリクエストに応答があることを確認してください。

続いては2台のノードを構成する。各サーバで以下の作業を行う。まずdockerのRPMパッケージをインストールし,その後マスタサーバと同じ設定を/etc/sysconfig/dockerに行う。
続いてkubernetes-nodeのRPMパッケージを導入し,

# yum -y install kubernetes-node-0.17.1-4.el7

以下のように設定ファイルを編集する。

#  /etc/kubernetes/configの次の行を編集
KUBE_MASTER="--master=http://master01:8080"

#  /etc/kubernetes/kubeletの次の行を編集
KUBELET_ADDRESS="--address=0.0.0.0"
#KUBELET_HOSTNAME="..."  ※コメントアウトする
KUBELET_API_SERVER="--api_servers=http://master01:8080"

設定ファイルを編集したら,kube-proxyとkubeletサービスをenable/startしてノードの構成は完了になる。

最後に,Flannelを用いてコンテナ接続用ネットワークを構成する。
Flannelはetcdに設定ファイルを格納する。まず,node01において,カレントディレクトリに以下のflannel-config.jsonを作成し,

{
  "Network":"10.1.0.0/16",
  "SubnetLen": 24,
  "Backend": {
    "Type": "vxlan",
    "VNI": 1
  }
}

次のコマンドで設定ファイルの内容をetcdに格納する。

# curl -L http://master01:4001/v2/keys/coreos.com/network/config -XPUT --data-urlencode value@flannel-config.json

ここで,curl -s -L http://master01:4001/v2/keys/coreos.com/network/config | python -mjson.toolコマンドで先ほどの設定が正しく格納されているか確認する。

この後は,node01とnode02で同じ作業を行う。まずyum -y install flannel-0.2.0-7.el7でRPMパッケージを導入し,/etc/sysconfg/flanneldを以下のように修正する。

FLANNEL_ETCD="http://master01:4001"

続いてflanneldサービスを有効化(enable)してサーバを再起動する。サーバが起動したら,ip a show flannel.1コマンドでflannel.1という仮想NICが構成されていることを確認する。

以上でKubernetes環境が構築できた。マスタサーバで以下のコマンドを実行することで,Kubernetesマスタが認識しているノードが表示される。

# kubectl get nodes
NAME     LABELS                          STATUS
node01   kubernetes.io/hostname=node01   Ready
node02   kubernetes.io/hostname=node02   Ready
Webサーバの負荷分散構成例

それでは,Webサーバの機能を提供するコンテナを複数起動して負荷分散を行う構成を試してみる。以下の操作は全てマスタサーバmaster01で行う。まず,テスト用のコンテナイメージをDocker Hubから取得して,プライベートレジストリに保存しておく。

# docker pull enakai00/centos:centos6
# docker tag enakai00/centos:centos6 master01:5000/enakai00/centos:centos6
# docker push master01:5000/enakai00/centos:centos6
// enakai00/test-httpd:ver1.0についても同様にpull/tag/pushを実行

test-httpd:ver1.0はApache HTTPサーバが導入されたコンテナイメージで,80番ポートでアクセスを受け付ける。hello.cgiにアクセスするとホスト名を返すようになっている。

Kubernetesでコンテナを起動する際はPodという単位で起動する。これは1つの仮想NICを共有する形で複数のコンテナをまとめたもので,同じPodに含まれるコンテナはlocalhostを経由して通信することが可能になる。例えば,PostgreSQLコンテナとその管理ツールであるpgadminのコンテナを1つのPodにまとめて起動すると,pgadminからはlocalhostで管理対象のPostgreSQLに接続できるようになる。

そして,Kubernetesではさまざまなリソースを定義することで環境を操作する。同一のコンテナイメージを用いて複数のPodを起動するには,Replication Controllerのリソースを定義する。以下の定義ファイルでは,test-httpdのPodを2個起動するhttpd-controllerという名前のReplication Controllerを定義している。

{
  "kind": "ReplicationController",
  "id": "httpd-controller",
  "apiVersion": "v1beta1",
  "labels": { "name": "httpd-controller" },
  "namespace": "default",
  "desiredState": {
    "replicaSelector": { "name": "httpd-pod" },     <- 複製するPodのラベルを指定
    "replicas": "2",     <- 複製するPodの個数
    "podTemplate": {
      "desiredState": {
        "manifest": {
          "id": "httpd",
          "containers": [    <- Podに含めるコンテナの指定
            {
              "image": "master01:5000/enakai00/test-httpd:ver1.0",
              "name": "httpd",
              "ports": [ { "containerPort": 80, "protocol": "TCP" } ]
            }
          ],
          "restartPolicy": { "always": {} },
          "version": "v1beta1",
          "volumes": null
        }
      },
      "labels": { "name": "httpd-pod" }  <- Podに付与するラベル
    }
  }
}

上記の内容のhttpd-controller.jsonをカレントディレクトリに置き,次のコマンドを実行すると,実際にReplication Controllerが定義されて,Podの起動が開始する。

# kubectl create -f httpd-controller.json

次は,定義されたReplication Controllerの一覧を表示するコマンドになる。

# kubectl get rc
CONTROLLER        CONTAINER(S)  IMAGE(S)                                     SELECTOR         REPLICAS
httpd-controller  httpd         master01:5000/enakai00/test-httpd:ver1.0     name=httpd-pod   2

また,次のコマンドで稼働中のPodが確認できる。

# kubectl get pod
// 2個のPodが稼働しており,それぞれのPodにはtest-httpdコンテナが含まれることが確認できる。
// それぞれのPodが稼働するノードは自動的に選択され,コンテナの起動が完了するとSTATUSがRUNNINGになる。

続いて起動したコンテナに外部からアクセスするためのリソースとしてサービスを定義する。
以下の定義ファイルを使用してサービスを定義すると,node01とnode02のどちらかのIPアドレスにポート番号999でアクセスしたときに,Pod内部のポート番号80に接続が転送されるようになる。

{
  "kind": "Service",
  "id": "httpd-service",
  "apiVersion": "v1beta1",
  "labels": { "name": "httpd-service" },
  "namespace": "default",
  "selector": { "name": "httpd-pod" },
  "containerPort": 80,
  "port": 999,
  "publicIPs": [ "<node1のIP>", "<node2のIP>" ]
}

上の内容のhttpd-service.jsonを作成し,次のコマンドで実際にサービスを定義する。

# kubectl create -f httpd-service.json

定義されたサービスの一覧はkubectl get serviceコマンドで確認できる。

# kubectl get service 
NAME             LABEL               SELECTOR         IP(S)            PORT(S)
httpd-service    name=httpd-service  name=httpd-pod   10.254.164.113   999/TCP
...

curl http://node1のIP:999/cgi-bin/hello.cgi でアクセスすると,コンテナに設定されたホスト名が返ってくる。繰り返しアクセスすると2種類のホストネームが得られるので,2個のPodに対して負荷分散されていることがわかる。

さて,先ほどサービスの一覧を確認したとき,httpd-serviceのIPアドレスとして10.254.164.113が表示されていた。これは,他
のコンテナからこのサービスに接続する際のIPアドレスになる。外部ネットワークから接続する際はノードのIPアドレスを指定するが,Kubernetesの管理するコンテナから通信する際はこちらのIPアドレスを指定することになる。また,サービスを定義した状態で新たなPodを起動すると,Podの内部ではこのIPアドレス環境変数に自動で設定されるので,アプリケーションからはこの環境変数を用いて他のコンテナが提供するサービスを利用することができる。

Replication Controllerを使わずに単体のPodを起動するには,Podのリソースを定義する。

{
  "kind": "Pod",
  "id": "centos",
  "apiVersion": "v1beta1",
  "labels": { "name": "centos" },
  "namespace": "default",
  "desiredState": {
    "manifest": {
      "id": "centos",
      "restartPolicy": { "always": {} },
      "version": "v1beta1",
      "volumes": null,
      "containers": [
        {
          "image": "master01:5000/enakai00/centos:centos6",
          "name": "centos6",
          "command": ["/bin/sleep", "6000"]
        }
      ]
    }
  }
}

上記のPod.jsonを作成し,以下のコマンドでPodを作成する。

# kubectl create -f pod.json

kubectl get podコマンドでPodの起動を確認できるので,続いてコンテナ内でbashを起動して環境変数の確認を行ってみる。

# kubectl exec -p centos -c centos6 -it /bin/bash
// 以下はコンテナ内でのコマンド
# env | grep HTTPD_SERVICE
HTTPD_SERVICE_PORT=tcp://10.254.164.113:999
...

kubectl execコマンドはdocker execに相当するコマンドで,-pオプションと-cオプションで実行対象のPodとコンテナを指名することで,指定のコンテナ内でコマンドを実行することができる。環境変数を見るとHTTPD_SERVICEで始まる各種の環境変数が設定されていることがわかる(ネーミングルールは--linkオプションと共通)。このコンテナ内で稼働するアプリケーションについては,これらの環境変数を用いて他のサービスにアクセスするように事前に構成しておけば,サービスのIPアドレスを意識する必要がなくなる。
なお,Podを削除するときは,kubectl delete pod centos で削除が可能である。


続いて,稼働中のPodの個数を変更してみる。Replication Contorllerから起動したPodについては,以下のコマンドで個数を変更することができる。

# kubectl resize --replicas=4 rc httpd-controller

これでkubectl get podコマンドの結果を見ると,Podの個数が増えていることがわかる。同様に,replicasの値を現象させることでPodの個数を減らす操作も行うことができる。

最後に,これまで起動したPodを停止して後片付けを行う。
Replication Controllerから起動したPodは,resizeコマンドでPodの個数を0に変更することでまとめて停止することができる。

# kubectl resize --replicas=0 rc httpd-controller

この後は,deleteコマンドでReplication Controllerとサービスの定義を削除しておく。

# kubectl delete rc httpd-controller
# kubectl delete service httpd-service