Fedora Atomic Host on EC2 with KubernetesでDockerコンテナのクラスタリング

はじめに

Redhatが本気でやってそうなので企業内で使うならばProject Atomicが最有力なのではないかなと、Fedora Atomic Hostを使ってEC2上でDockerコンテナのクラスタリングを試してみました。

タイトルはKubernetesと書いていますが、Atomicに入っているCockpitとかも一通り試します。

Project Atomicが何者かは、このスライド「Docker on RHEL & Project Atomic 入門 – #Dockerjp 4」が大変わかりやすいです。

試した時点の各ソフトウェアのバージョン

だいぶ前ですが、試したのは2015年2月17日なので以下の情報はその日付時点の情報です。

Fedora-Cloud-Atomic-20141203-21.x86_64でrpm-ostree upgradeで入った最新バージョンです。

  • Kubernetes: 0.7.0
  • Cockpit: 0.27
  • etcd: 0.4.6
  • docker: 1.4.1
  • cAdvisor: 0.6.2

最新は以下なので、かなり古い。
まあ日々バージョンアップしているので仕方ないか。

  • Kubernetes: 0.10.1
  • Cockpit: 0.38
  • etcd: 2.0.3
  • docker: 1.5.0
  • cAdvisor: 0.9.0

rpm-ostree upgradeすると上のより新しいバージョンが入りますし、最新のバージョンも変わっていますが、この記事の内容を試した時点ということでお許し下さい。

EC2インスタンスのローンチ

RHEL Atomic, CentOS Atomicもありますが、今回は一番開発が早そうなFedora Atomicを使ってみます。

Fedora Cloud のダウンロードの一番下の「Atomic Cloud HVM」から好きなリージョンのAMIを選択。

スクリーンショット 2015-02-16 14.48.44.png

普通にローンチします。
あとでクラスタ組むときに使うので、セキュリティグループには適当な名前をつけておいてください。

スクリーンショット_2015-02-16_14_49_01.png

SSHログインする際のユーザはfedoraです。

$ ssh -i ~/.ssh/key.pem fedora@1.2.3.4

Kubernetesなどのバージョンが古いので、パッケージのアップデートをします。

$ sudo rpm-ostree upgrade
$ sudo systemctl reboot

Dockerコンテナを動かしてみる

Dockerもちゃんと動いています。

$ systemctl list-units | grep docker   
sys-devices-virtual-net-docker0.device                                                    loaded active plugged   /sys/devices/virtual/net/docker0
sys-subsystem-net-devices-docker0.device                                                  loaded active plugged   /sys/subsystem/net/devices/docker0
docker.service                                                                            loaded active running   Docker Application Container Engine

docker runしてみます。

$ sudo docker run centos /bin/echo "Hello Atomic"
Unable to find image 'centos' locally
Pulling repository centos
dade6cb4530a: Download complete 
511136ea3c5a: Download complete 
5b12ef8fd570: Download complete 
Status: Downloaded newer image for centos:latest
Hello Atomic

動きました。

Cockpitを動かしてみる

CockpitはサーバーおよびDockerコンテナを管理できるWebインターフェースです。
Atomicではデフォルトで動いています。

Cockpitは9090ポートで動くので、セキュリティグループで9090を開けます。

スクリーンショット 2015-02-16 15.24.00.png

OSのユーザアカウントでログインするので、rootユーザにパスワードを設定します。

$ sudo passwd root

http://1.2.3.4:9090にアクセス

スクリーンショット 2015-02-16 15.30.11.png

先ほどパスワードを使ってrootユーザでログインすると、クラスタ内のサーバー一覧が表示されます。

スクリーンショット 2015-02-16 15.40.06.png

各リソースの利用状況を監視できます。

スクリーンショット 2015-02-16 15.40.13.png

Dockerコンテナ・イメージの管理もできる

スクリーンショット 2015-02-16 16.08.58.png

Dockerコンテナの管理はかなりいろいろできます。

コンテナの起動・停止
(惜しいことにdocker runのオプションの指定はできない。)

スクリーンショット 2015-02-16 22.42.44.png

コンテナ毎にシステムリソースの制限を設定

スクリーンショット 2015-02-16 22.32.49.png

Docker Hubからイメージをpull

スクリーンショット 2015-02-16 22.38.32.png

他にも「サーバーのターミナルをWebから操作」「全コンテナ合計のCPU・メモリ使用率の参照」「イメージからコンテナの起動」などができます。

cAdvisorを動かしてみる

cAdvisorはサーバー・コンテナ監視のツールです。デフォルトで起動はしないので、起動します。

$ sudo systemctl start cadvisor

動きました。

$ sudo journalctl -f -l -xn -u cadvisor
-- Logs begin at Mon 2015-02-16 13:19:03 UTC. --
Feb 18 02:49:49 ip-172-30-0-246.ec2.internal cadvisor[1331]: I0218 02:49:49.443643    1331 manager.go:77] cAdvisor running in container: "/"
Feb 18 02:49:49 ip-172-30-0-246.ec2.internal cadvisor[1331]: I0218 02:49:49.444400    1331 manager.go:91] Machine: {NumCores:1 MemoryCapacity:1040220160 Filesystems:[{Device:/dev/xvda1 Capacity:198902784}]}
Feb 18 02:49:49 ip-172-30-0-246.ec2.internal cadvisor[1331]: I0218 02:49:49.444749    1331 manager.go:98] Version: {KernelVersion:3.18.6-200.fc21.x86_64 ContainerOsVersion:Fedora 21 (Twenty One) DockerVersion:Unknown CadvisorVersion:0.6.2}
Feb 18 02:49:49 ip-172-30-0-246.ec2.internal cadvisor[1331]: E0218 02:49:49.444973    1331 cadvisor.go:62] Docker registration failed: unable to communicate with docker daemon: dial unix /var/run/docker.sock: no such file or directory.
Feb 18 02:49:49 ip-172-30-0-246.ec2.internal cadvisor[1331]: I0218 02:49:49.445578    1331 factory.go:78] Registering Raw factory
Feb 18 02:49:49 ip-172-30-0-246.ec2.internal cadvisor[1331]: I0218 02:49:49.446156    1331 manager.go:394] Added container: "/" (aliases: [], namespace: "")
Feb 18 02:49:49 ip-172-30-0-246.ec2.internal cadvisor[1331]: I0218 02:49:49.446334    1331 manager.go:131] Starting recovery of all containers
Feb 18 02:49:49 ip-172-30-0-246.ec2.internal cadvisor[1331]: I0218 02:49:49.446922    1331 manager.go:136] Recovery completed
Feb 18 02:49:49 ip-172-30-0-246.ec2.internal cadvisor[1331]: I0218 02:49:49.447805    1331 cadvisor.go:103] Starting cAdvisor version: "0.6.2" on port 4194
Feb 18 02:49:49 ip-172-30-0-246.ec2.internal cadvisor[1331]: I0218 02:49:49.448019    1331 container.go:141] Start housekeeping for container "/"

4194ポートで動いているので、4194ポートを開けて、http://<IPアドレス>:4194にアクセスします。

スクリーンショット 2015-02-23 1.32.03.png

サーバーのリソースやDockerコンテナのリソースを見ることができます。
APIもあるので監視の仕組みに使えると思います。
(Dockerコンテナを見ようとするとなぜかエラーになりましたが、とりあえず見なかったことにしました。)

最新バージョン(v0.9.0)だとネットワークやDiskIOの監視もできるようです。(Releases · google/cadvisor

なんかCockpitと機能がかぶっていますが、cAdvisorはKubernetesが使っているのでしょうがないですね。

Kubernetesを動かしてみる

KubernetesはDockerコンテナのクラスタリングツールです。CoreOSのfleetに当たります。

Testing Kubernetes with an Atomic Host — Project Atomicを参考にKubernetesを動かします。

このバージョン(etcd 0.4.6)ではデフォルトの設定ではetcdが起動しないので設定を変更する。
/etc/etcd/etcd.confnameのコメントを外す。

$ sudo vi /etc/etcd/etcd.conf
name = "default-name"

Kubernetesはインストール時は動いていないので手動で動かします。

$ sudo su
# for SERVICES in etcd kube-apiserver kube-controller-manager  kube-scheduler docker kube-proxy.service  kubelet.service; do 
         systemctl restart $SERVICES
         systemctl enable $SERVICES
         systemctl status $SERVICES
     done

etcdが起動していることを確認します。

$ curl http://localhost:4001/version
etcd 0.4.6

Kubernetesのapiserverが起動していることを確認します。

$ curl  http://localhost:8080/version
{
  "major": "0",
  "minor": "7+",
  "gitVersion": "v0.7.0-18-g52e165a4fd720d-dirty",
  "gitCommit": "52e165a4fd720d1703ebc31bd6660e01334227b8",
  "gitTreeState": "dirty"
}

まだクラスタは組んでいないのでminionは自分だけです。

$ kubectl get minions
NAME                LABELS
127.0.0.1           <none>

Apacheのpodを起動してみます。

$ vi apache.json
{
  "id": "fedoraapache",
  "kind": "Pod",
  "apiVersion": "v1beta1",
  "desiredState": {
    "manifest": {
      "version": "v1beta1",
      "id": "fedoraapache",
      "containers": [{
        "name": "fedoraapache",
        "image": "fedora/apache",
        "ports": [{
          "containerPort": 80,
          "hostPort": 80
        }]
      }]
    }
  },
  "labels": {
    "name": "fedoraapache"
  }
}
$ kubectl create -f apache.json
I0216 16:00:07.854098    6240 restclient.go:133] Waiting for completion of operation 2
fedoraapache

Apacheが動きました。

$ kubectl get pod fedoraapache
NAME                IMAGE(S)            HOST                LABELS              STATUS
fedoraapache        fedora/apache       127.0.0.1/          name=fedoraapache   Running
$ curl http://localhost/
Apache

Kubernetesでクラスタ環境を構築

kubernetes/fedora_manual_config.md at master · GoogleCloudPlatform/kubernetesを参考に手動で設定します。

インスタンスを一台増やして一台をmaster、残りをminionとしてクラスタを組みます。以下の構成とします。

fed-master = 172.30.0.246
fed-minion = 172.30.0.43

このとき追加したインスタンスは全部同じセキュリティグループに所属させてください。

追加したインスタンスもパッケージを更新します。

$ sudo rpm-ostree upgrade
$ sudo systemctl reboot

上の記事ではFedora Atomicでは必要ないと書かれているのですが、同じエラーが出るのでバグ対応します。

$ sudo su
# sed -e "s/docker\.socket/docker\.service/g" /usr/lib/systemd/system/kubelet.service > /etc/systemd/system/kubelet.service
# systemctl daemon-reload

全ノードのhostsにサーバーのIPを書きます。(DNSサーバーにホスト名が登録されていれば必要はありません。)

$ sudo su
# echo "172.30.0.246 fed-master
172.30.0.43  fed-minion" >> /etc/hosts

全ノードの/etc/kubernetes/configを以下のように変更します。
KUBE_ETCD_SERVERSの設定のみ変更すれば大丈夫なはずです。

# Comma seperated list of nodes in the etcd cluster
KUBE_ETCD_SERVERS="--etcd_servers=http://fed-master:4001"

# logging to stderr means we get it in the systemd journal
KUBE_LOGTOSTDERR="--logtostderr=true"

# journal message level, 0 is debug
KUBE_LOG_LEVEL="--v=0"

# Should this cluster be allowed to run privleged docker containers
KUBE_ALLOW_PRIV="--allow_privileged=false"

同じセキュリティグループ内で全てのTCP通信をOKに設定します。

スクリーンショット 2015-02-17 14.18.14.png

masterノードの設定

前に起動したApacheのPodを停止しておきます。

$ kubectl delete -f apache.json 
fedoraapache

masterノードに不必要なサービスを停止します。

$ sudo su
# for SERVICES in kube-proxy kubelet docker; do 
    systemctl stop $SERVICES
    systemctl disable $SERVICES
    systemctl status $SERVICES 
done

masterノードの/etc/kubernetes/apiserverを以下のように変更します。

# The address on the local server to listen to.
KUBE_API_ADDRESS="--address=0.0.0.0"

# The port on the local server to listen on.
KUBE_API_PORT="--port=8080"

# How the replication controller and scheduler find the kube-apiserver
KUBE_MASTER="--master=http://fed-master:8080"

# Port minions listen on
KUBELET_PORT="--kubelet_port=10250"

# Address range to use for services
KUBE_SERVICE_ADDRESSES="--portal_net=10.254.0.0/16"

# Add you own!
KUBE_API_ARGS=""

masterノードの/etc/kubernetes/controller-managerを以下のように変更します。

# Comma seperated list of minions
KUBELET_ADDRESSES="--machines=fed-minion"

masterノードで関連サービスを起動します。

$ sudo su
# for SERVICES in etcd kube-apiserver kube-controller-manager kube-scheduler; do 
    systemctl restart $SERVICES
    systemctl enable $SERVICES
    systemctl status $SERVICES 
done

minionノードの設定

/etc/kubernetes/kubeletを以下のように設定します。

# The address for the info server to serve on
KUBELET_ADDRESS="--address=0.0.0.0"

# The port for the info server to serve on
KUBELET_PORT="--port=10250"

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

# Add your own!
KUBELET_ARGS=""

必要なサービスを起動します。

$ sudo su
# for SERVICES in kube-proxy kubelet docker; do 
    systemctl restart $SERVICES
    systemctl enable $SERVICES
    systemctl status $SERVICES 
done

masterノードで以下のコマンドを実行し、fed-minionがクラスタに追加されていることを確認します。

$ kubectl get minions
NAME                LABELS
fed-minion          <none>

Apache Podを作成してみます。

$ kubectl create -f apache.json 
fedoraapache

ちゃんとfed-minionノードで起動しました。

$ kubectl get pod fedoraapache
NAME                IMAGE(S)            HOST                LABELS              STATUS
fedoraapache        fedora/apache       fed-minion/         name=fedoraapache   Running
$ curl http://fed-minion/
Apache

クラスタ構成が組めました。

しかし、ここでCockpitを見てもmasterノードしか表示されない。最新のCockpitではKubernetes連携もマルチホスト対応が書かれているので何か違うかもしれない。(Releases · cockpit-project/cockpit

minionを2つにする

1インスタンス追加します。
上と同じ手順で1インスタンス追加してください。

fed-master = 172.30.0.246
fed-minion = 172.30.0.43
fed-minion-2 = 172.30.0.11

fed-minion-2の/etc/hostsに各ノードのIPを設定します。

# echo "172.30.0.246 fed-master
172.30.0.43  fed-minion
172.30.0.11  fed-minion-2" >> /etc/hosts

fed-masterとfed-minionの/etc/hostsにも以下の設定を追加します。

# echo "172.30.0.11  fed-minion-2" >> /etc/hosts

fed-masterの/etc/kubernetes/controller-managerの以下の箇所にfed-minion-2を追加します。

KUBELET_ADDRESSES="--machines=fed-minion,fed-minion-2"

fed-minion-2の/etc/kubernetes/configKUBE_ETCD_SERVERSを以下のように変更します。

KUBE_ETCD_SERVERS="--etcd_servers=http://fed-master:4001"

fed-minion-2の/etc/kubernetes/kubeletの以下の2箇所を変更します。

KUBELET_ADDRESS="--address=0.0.0.0"
KUBELET_HOSTNAME="--hostname_override=fed-minion-2"

fed-minion-2の必要なサービスを起動します。

$ sudo su
# for SERVICES in kube-proxy kubelet docker; do 
    systemctl restart $SERVICES
    systemctl enable $SERVICES
    systemctl status $SERVICES 
done

fed-masterのサービスを再起動します。

$ sudo su
# for SERVICES in etcd kube-apiserver kube-controller-manager kube-scheduler; do 
    systemctl restart $SERVICES
    systemctl enable $SERVICES
    systemctl status $SERVICES 
done

クラスタにfed-minion-2が追加されました。

$ kubectl get minions
NAME                LABELS
fed-minion-2        <none>
fed-minion          <none>

Guestbookサンプルを動かす

再び、Testing Kubernetes with an Atomic Host — Project Atomicを参考に進め、このクラスタ構成でKubernetesに付いているGuestbookサンプルを動かします。

GuestbookはPHPとRedisで構成されているデモアプリで、コメントを書き込むとRedisに保存されます。
フロントエンドのコンテナは3つ、Redisのマスターが1つ、読み込み専用のRedisのスレーブが2つという構成です。PHPのアプリは読み込みはスレーブから、書き込みはマスターに行います。

今回はminion2つで試すので、フロントエンドも2つに減らします。以下のような構成です。

スクリーンショット 2015-02-26 11.17.30.png

Release v0.7.0 Release Candidate · GoogleCloudPlatform/kubernetesからkubernetesのGitリポジトリをダウンロードします。一応インストールされているバージョンと同じv0.7.0にしておきます。
なお、ここからの作業は全てfed-masterノードで行います。

$ curl -L -O https://github.com/GoogleCloudPlatform/kubernetes/releases/download/v0.7.0/kubernetes.tar.gz
$ tar zxvf kubernetes.tar.gz 
$ cd kubernetes/examples/guestbook

RedisのマスターPodを起動します。

$ kubectl create -f redis-master.json

get pod redis-masterRuningになるまで待ちます。

$ kubectl get pod redis-master
NAME                IMAGE(S)            HOST                LABELS              STATUS
redis-master        dockerfile/redis    fed-minion/         name=redis-master   Running

redis-masterというラベルのついたPodに通信するサービスを作成します。
KubernetesのServiceはコンテナ間通信を仲介するロードバランサーです。コンテナからは環境変数を通してサービスを使うことができます。

$ kubectl create -f redis-master-service.json

Serviceが作成されたことを確認します。

$ kubectl get service redis-master        
NAME                LABELS              SELECTOR            IP                  PORT
redis-master        name=redis-master   name=redis-master   10.254.104.212      6379

これで全てのPodはredisマスタに10.254.104.212:6379でアクセスできるようになります。

Redisマスタは単一のPodでしたが、Redisスレーブは2つのPodを維持するよう設定されています。これはreplicated podと呼ばれ、ReplicationControllerが管理します。

以下のコマンドでRedisスレーブを起動します。

$ kubectl create -f redis-slave-controller.json

ReplicationContorlllerPodが2つ起動しています。
Redisスレーブはfed-minionとfed-minion-2の両方で1つずつ動きます。

$ kubectl get replicationController redisSlaveController
NAME                   IMAGE(S)                   SELECTOR            REPLICAS
redisSlaveController   brendanburns/redis-slave   name=redisslave     2
$ kubectl get pods -l "name=redisslave"
NAME                                   IMAGE(S)                   HOST                LABELS                              STATUS
36300455-b6a8-11e4-8444-12e8df01b920   brendanburns/redis-slave   fed-minion-2/       name=redisslave,uses=redis-master   Running
3630b03b-b6a8-11e4-8444-12e8df01b920   brendanburns/redis-slave   fed-minion/         name=redisslave,uses=redis-master   Running

RedisスレーブのDockerコンテナのコマンドは以下のように環境変数からRedisマスタのホスト名とポート番号を取得するようになっています。これはredis-masterサービスで解決されます。

redis-server --slaveof ${REDIS_MASTER_SERVICE_HOST:-$SERVICE_HOST} $REDIS_MASTER_SERVICE_PORT

RedisスレーブPodRunningになったら、RedisスレーブServiceを作成します。

$ kubectl create -f redis-slave-service.json
$ kubectl get service redisslave
NAME                LABELS              SELECTOR            IP                  PORT
redisslave          name=redisslave     name=redisslave     10.254.197.146      6379

RedisマスタServiceと同じように、どのPod内からもRedisスレーブに10.254.197.146:6379でアクセスできるようになります。Serviceがロードバランシングし、2つのPodに透過的にアクセスできます。

最後にフロントエンドPodを作成します。サンプルではフロントエンドPodは3つのPodで構成されているのですが、minionが2つしかないので、frontend-controller.jsonreplicasを3から2に変更します。

    "replicas": 2,

最後にフロントエンドPodを作成します。

$ kubectl create -f frontend-controller.json
$ kubectl get replicationController frontendController
NAME                 IMAGE(S)                                 SELECTOR            REPLICAS
frontendController   kubernetes/example-guestbook-php-redis   name=frontend       2
$ kubectl get pods -l "name=frontend"
NAME                                   IMAGE(S)                                 HOST                LABELS                                       STATUS
b8947bd1-b6ac-11e4-8444-12e8df01b920   kubernetes/example-guestbook-php-redis   fed-minion-2/       name=frontend,uses=redisslave,redis-master   Running
b89561c0-b6ac-11e4-8444-12e8df01b920   kubernetes/example-guestbook-php-redis   fed-minion/         name=frontend,uses=redisslave,redis-master   Running

PHPのコードの中ではgetenv('REDIS_MASTER_SERVICE_PORT');のように環境変数からRedisマスタとスレーブのホスト名、ポートを取得しています。詳しくはguestbook/php-redis/index.phpを見てください。

これで全てのPod, Service, Replication Controllerが起動しました。

$ kubectl get pods
NAME                                   IMAGE(S)                                 HOST                LABELS                                       STATUS
redis-master                           dockerfile/redis                         fed-minion/         name=redis-master                            Running
36300455-b6a8-11e4-8444-12e8df01b920   brendanburns/redis-slave                 fed-minion-2/       name=redisslave,uses=redis-master            Running
3630b03b-b6a8-11e4-8444-12e8df01b920   brendanburns/redis-slave                 fed-minion/         name=redisslave,uses=redis-master            Running
b8947bd1-b6ac-11e4-8444-12e8df01b920   kubernetes/example-guestbook-php-redis   fed-minion-2/       name=frontend,uses=redisslave,redis-master   Running
b89561c0-b6ac-11e4-8444-12e8df01b920   kubernetes/example-guestbook-php-redis   fed-minion/         name=frontend,uses=redisslave,redis-master   Running

frontendが起動しているfed-minionからfed-minion-2に8000ポートでアクセスするとGuestbookアプリケーションが動きます。(事前にEC2のセキュリティグループで8000を開けておいてください。)

スクリーンショット 2015-02-17 23.06.07.png

自動フェイルオーバーを試してみる

fed-minionインスタンスを停止してみる。

$ kubectl get minions
NAME                LABELS
fed-minion-2        <none>

minionは減った。

$ kubectl get pods
F0217 15:08:54.544116    1605 get.go:75] The requested resource does not exist.

あれ。。。
これかな。kubectl/apiserver problems if minion down · Issue #2951 · GoogleCloudPlatform/kubernetes

CoreOSとの比較

ChromeOS autoupdate vs rpm-ostree

シンプルさと柔軟性のトレードオフという印象
参考:OSTree: OSイメージとパッケージシステムの間にGitのアプローチを

fleet vs Kubernetes

Atomic同梱のバージョンだとfleetの方が安定している。Kubernetesはバギー。
日々リリースされているので、最新バージョンだともっといいんだろうけど。

けっこう大きな違いだと思うのはmasterサーバーの要不要。
Kubernetesはmasterサーバーが落ちたらコンテナは動いているけど、クラスタ管理機能が使えなくなる。
CoreOSの場合は全サーバーが同じ立場なので、どれが落ちても何も変わらない。
と理解しているが、合っているだろうか。

でも、Kubernetesの方が機能が豊富

  • コンテナ間通信はKubernetesのServiceで実現
  • Heapsterでクラスタ内コンテナのリソース監視
  • fluentd+Elasticsearch+Kibanaによるログ収集・分析

Cockipit

Cockpit相当の機能はCoreOSにはない。
1ノード完結でDockerを動かす需要がどれほどあるかわからないが、それであればCockpitは十分使えそう。バギーでもなかった。

Kubernetes対応については、Cockpitの0.37でKubernetesプラグインが追加されていたり、さらに開発も進めているようなので期待したい。(今回はAtomicを試しただけなので、最新のCockpitは試していないです。)

Atomicの感想

デフォルトでKubernetes, etcd, cAdviserが起動せず、DockerとCockpitしか起動していないあたりからも、今のところAtomicは1ノードでのDockerコンテナ管理のためのホストOSとして作られている印象。

Dockerコンテナのクラスタリングは完全にKubernetes任せで、今のところはインストールしといたから後はKubernetesで好きにやってという感じに見える。

まとめると、1ノードなら使える(まあDockerにCockpit付いただけ)、クラスタ構成はまだまだこれからという感想です。

参考

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中