Posted at

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

More than 3 years have passed since last update.


はじめに

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を選択。

普通にローンチします。

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

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を開けます。

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

$ sudo passwd root

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

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

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

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

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

コンテナの起動・停止

(惜しいことにdocker runのオプションの指定はできない。)

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

Docker Hubからイメージをpull

他にも「サーバーのターミナルを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にアクセスします。

サーバーのリソースや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に設定します。


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つに減らします。以下のような構成です。

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を開けておいてください。)


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

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付いただけ)、クラスタ構成はまだまだこれからという感想です。


参考