Docker オーケストレーションツールの一つに Kubernetes (k8s) があります。k8s 実行環境の構築方法は複数ありますが、ここでは AWS や GCP 等のクラウドサービスを利用せず、Debian9 がインストールされた複数台の物理マシンがネットワーク内に存在する状況を考えます。これら複数の Debian9 上に k8s クラスタを立ち上げるためには Rancher が利用できます。k8s 以外の用途で利用しないことが分かっている場合は、Debian9 上で Rancher を利用するのではなく専用の RancherOS を利用することもできます。
ここでは実験のため VirtualBox で二つの Debian9 マシンを起動して、それらを同じ NAT ネットワーク 10.0.2.0/24
に所属させます。
10.0.2.4/24
10.0.2.5/24
簡単のため Docker イメージとして提供される Rancher を利用します。Docker エンジンをインストールします。
sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/debian \
$(lsb_release -cs) \
stable"
sudo apt-get update
sudo apt-get install docker-ce
sudo なしで docker コマンドを利用できるように設定できます。
sudo groupadd docker
sudo usermod -aG docker $USER
exit # ログインしなおし
docker ps
以下のコマンドで Rancher を起動します。データを永続化するための -v
、および後に Node 追加のために起動する rancher-agent コンテナへのポート割り当てを考慮した -p
を指定します。
docker run -d --name=rancher --restart=unless-stopped \
-v /host/rancher:/var/lib/rancher \
-p 8080:80 \
-p 8443:443 \
rancher/rancher:stable
ブラウザからアクセスして admin
の初期パスワードを設定してログインします https://127.0.0.1:8080
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cc89ae5eac4a rancher/rancher:stable "entrypoint.sh" 28 minutes ago Up 28 minutes 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp rancher
Rancher Server URL にはホスト名など、ネットワーク内の他のマシンおよびローカルからアクセスできるものを指定します。更に必要な場合は nginx 等でリバースプロキシ設定を行います。X-Forwarded-Proto
を指定することで、Rancher 側では http から https へのリダイレクトを行わなくなります。
ブラウザから「Add Cluster」→「CUSTOM」→「Cluster Name: mycluster」→「Next」として k8s クラスタの設定を追加します。
k8s クラスタに追加する Node を起動するための docker run コマンドを生成するためのオプションとして「etcd, Control, Worker」すべてにチェックを入れると以下のようなコマンドが表示されます。
sudo docker run -d --privileged --restart=unless-stopped --net=host -v /etc/kubernetes:/etc/kubernetes -v /var/run:/var/run rancher/rancher-agent:v2.1.1 --server https://10.0.2.4:8443 --token 9c5p642z52p7tllfjzc2hgfwkb5xmck6266vdlvs82lfc8crwbbsd6 --ca-checksum a42937938e0c38ba4e56845260c8105017205e886677fa0c6c5d020bf6a9ed93 --etcd --controlplane --worker
これを Rancher を起動したホストで実行すると、同じ Debian マシン上に Rancher サーバコンテナに加えて、Rancher-agent コンテナおよびその他必要なコンテナが起動した状態になります。
$ docker ps | awk '{ print $2 }' | grep rancher | sort | uniq
rancher/coreos-etcd:v3.2.18
rancher/hyperkube:v1.11.3-rancher1
rancher/metrics-server-amd64
rancher/pause-amd64:3.1
rancher/rancher:stable
同様に別の Debian マシンで上記コマンドによって Rancher-agent を起動して Rancher サーバに接続することで、クラスタに Node を追加できます。以下の画像は debian1 10.0.2.4/24
と debian2 10.0.2.5/24
で Node 数が 2 の状態です。Node の追加が正常に完了するまでは Alert が表示された状態になります。
k8s クラスタで Docker コンテナを走らせるためには、Rancher の WebUI および HTTP API に加えて、標準の kubectl コマンドも利用できます。kubectl は Rancher ではなく k8s 標準のコマンドです。クラスタを複数台のマシン間で組む必要がない場合は microk8s や minikube を利用すると、ローカルマシン上に k8s クラスタを比較的簡単に構築でき、kubectl コマンドの動作確認等が行えます。以下では Rancher で起動した k8s クラスタを kubectl から利用するコマンドサンプルを記載します。
kubectl は k8s クラスタのクライアントコマンドです。OS に応じて apt、yum、brew 等でインストールできます。Debian9 の場合は以下のようになります。
sudo apt-get update && sudo apt-get install -y apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee -a /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubectl
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
Web UI から取得したサーバへの接続情報を ~/.kube/config
にコピーして接続確認します。kubectl クライアントと k8s クラスタのバージョンは Minor の一つ違いまで動作することが保証されています。例えば v1.2 クライアントは v1.1, v1.2, v1.3 クラスタに対して利用できます。
$ kubectl cluster-info
Kubernetes master is running at https://192.168.56.11:8443/k8s/clusters/c-zt2l5
KubeDNS is running at https://192.168.56.11:8443/k8s/clusters/c-zt2l5/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"12", GitVersion:"v1.12.2", GitCommit:"17c77c7898218073f14c8d573582e8d2313dc740", GitTreeState:"clean", BuildDate:"2018-10-24T06:54:59Z", GoVersion:"go1.10.4", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"11", GitVersion:"v1.11.3", GitCommit:"a4529464e4629c21224b3d52edfe0ea91b072862", GitTreeState:"clean", BuildDate:"2018-09-09T17:53:03Z", GoVersion:"go1.10.3", Compiler:"gc", Platform:"linux/amd64"}
また、Rancher の WebUI に実装されている簡易シェルからも kubectl コマンドが利用できます。
k8s クラスタは複数のコンポーネントで構成されます。コンポーネントはクラスタを構成するマシン上で実行されます。これらマシンを Node とよびます。k8s は実行するコンポーネントを etcd、Control Plane、Worker の三つのロールに分けて管理しており、上記の通り Node となる各マシンで rancher/rancher-agent
を docker run するときの --etcd --controlplane --worker
オプションで選択できるようになっています。"Node" という表現が重複しますが、コンポーネントを Master コンポーネントと Node コンポーネントで分けた場合において、etcd は Master コンポーネントに分類されます。
Worker で実行するユーザアプリケーションは Pod という単位で管理されます。Pod は一つ以上の Docker コンテナおよび必要となるファイル等から成ります。Worker では複数の Pod を同時に実行できます。Worker には Kubelet というプロセスが常駐しており、コンテナの状態等を監視します。
kubectl get nodes
NAME STATUS ROLES AGE VERSION
debian1 Ready controlplane,etcd,worker 6d v1.11.3
debian2 Ready controlplane,etcd,worker 5d v1.11.3
設定ファイルを基本とした kubectl create
によるクラスタ操作の方法と、設定ファイルを用いない kubectl run
による方法があります。状況に応じてメリットとデメリットを考慮しながら使い分けます。
kubectl run mydeployment --image=nginx
kubectl get deployments
kubectl describe deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
mydeployment 1 1 1 1 3m
自動的にデプロイ設定に応じた Pod も作成されます。
kubectl get pods
kubectl describe pods
NAME READY STATUS RESTARTS AGE
mydeployment-65b9f8795d-r4hrs 1/1 Running 0 4m
kubectl expose deployment/mydeployment --type="NodePort" --port 80
kubectl get services
kubectl describe services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 10d ←k8s作成時から存在
mydeployment NodePort 10.43.89.123 <none> 80:31685/TCP 6s ←今回新規作成
nginx のサービスを利用してみます。
kubectl cluster-info
curl 192.168.56.11:31685
NAT の外部 IP は以下のコマンドで取得できます。
kubectl get services/mydeployment -o go-template='{{(index .spec.ports 0).nodePort}}'
サービス
kubectl delete services mydeployment
デプロイ (Pod も合わせて削除されます)
kubectl delete deployments mydeployment
kubectl get pods
NAME READY STATUS RESTARTS AGE
mydeployment-65b9f8795d-r4hrs 0/1 Terminating 0 12m
Pod 名の確認
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
mydeployment-65b9f8795d-8r4pl 1/1 Running 0 16m 10.42.0.8 debian <none>
tailf のように監視できます。
kubectl logs mydeployment-65b9f8795d-8r4pl -f
Pod 削除が完了するまで待つ
kubectl delete services,deployments,jobs,pods,cronjobs --all
Terminate されたことの確認を待たない
kubectl delete services,deployments,jobs,pods,cronjobs --all --grace-period 0 --force
echo "kubectl get pods -o go-template --template '[[range .items]][[.metadata.name]][[\"\\n\"]][[end]]'" | tr '[' '{' | tr ']' '}'
mydeployment-65b9f8795d-vb72j
mydeployment2-6dcc9fb68-9gbbs
kubectl get pods -o name
pod/mydeployment-65b9f8795d-vb72j
pod/mydeployment2-6dcc9fb68-9gbbs
-c
を指定しない場合は Pod 内のコンテナが一つ選択されます。
kubectl exec -it mydeployment-65b9f8795d-dpt96 /bin/bash
kubectl exec mydeployment-65b9f8795d-dpt96 -- ls /
kubectl exec mydeployment-65b9f8795d-dpt96 -- /bin/bash -c "ls && ls"
kubectl proxy --port 8001
curl http://localhost:8001/
Rancher から利用する場合は /k8s/clusters/ を付けます。例えば API OVERVIEW に記載の namespaces
一覧を取得する例は以下のようになります。
curl http://127.0.0.1:8001/k8s/clusters/<CLUSTER_ID>/api/v1/namespaces
環境変数の設定
kubectl run mydeployment --image=nginx --env="MYENV1=xxx" --env="MYENV2=yyy"
kubectl exec mydeployment-6c6b59798f-2bqp4 -- /bin/bash -c 'echo $MYENV1'
イメージのコマンドを上書き
kubectl run mydeployment --image=nginx --restart=Never -it --command -- /bin/bash -c "ls /"
kubectl run mydeployment --image=nginx --restart=Never -it --command -- /bin/bash
--restart=Never
を付与すると Pod のみ作成されます。デプロイ設定は作成されません。
kubectl run mydeployment --image=nginx --restart=Never
kubectl get pods
kubectl get deployments
Deployment や Job を利用せず直接 Pod だけを create できます。
apiVersion: v1
kind: Pod
metadata:
labels:
run: mypod
name: mypod
spec:
containers:
- image: nginx
name: nginx
restartPolicy: Never
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mypod 1/1 Running 0 15s
kubectl run mydeployment --image=nginx --restart=Never --schedule="0/2 * * * *" --command -- /bin/bash -c "date"
kubectl get cronjobs
kubectl delete cronjobs --all
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
mydeployment 0/2 * * * * False 0 <none> 20s
デプロイ設定は作成されません。
$ kubectl get deployments
No resources found.
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mydeployment-1542640920-rhphc 0/1 Completed 0 8s
$ kubectl logs mydeployment-1542640920-rhphc
Mon Nov 19 15:22:14 UTC 2018
例えば --cap-add ALL --privileged
、--name xxx
、-h yyy
に相当する設定は以下のようになります。--env
で指定した環境変数の値は無視されるため JSON 内に記載します。以下では特権モードにした状態でホストのデバイスファイルをマウントしています。
kubectl run mydeployment --image=nginx --overrides '{
"spec": {
"template": {
"spec": {
"hostname": "yyy",
"containers": [
{
"name": "xxx",
"image": "nginx",
"env": [
{"name":"MYENV", "value":"zzz"}
],
"securityContext": {
"capabilities": {
"add": ["ALL"]
},
"privileged": true
},
"volumeMounts": [
{
"mountPath": "/dev/fuse",
"name": "devfuse"
}
]
}
],
"volumes": [
{
"name": "devfuse",
"hostPath": {
"path": "/dev/fuse"
}
}
]
}
}
}
}'
kubectl exec -it `kubectl get pods -l run=mydeployment -o name | cut -d/ -f2` -- /bin/bash -c 'hostname'
yyy
kubectl exec -it `kubectl get pods -l run=mydeployment -o name | cut -d/ -f2` -- /bin/bash -c 'ls -l /dev/fuse'
crw-rw-rw- 1 root root 10, 229 Sep 7 08:28 /dev/fuse
kubectl exec -it `kubectl get pods -l run=mydeployment -o name | cut -d/ -f2` -- /bin/bash -c 'echo $MYENV'
zzz
kubectl describe pods -l run=mydeployment | grep -A1 Containers:
Containers:
xxx:
docker の ENTRYPOINT は command
で変更できます。CMD は args
で指定できます。
kubectl run mydeployment --image=nginx --overrides '{
"spec": {
"template": {
"spec": {
...
"containers": [
{
...
"command": ["/bin/bash", "-c"],
"args": ["echo 123 && echo 123"]
}
],
既定では imagePullPolicy
が IfNotPresent
になっているため、:latest
タグを指定している場合等を除いて、既に同名のイメージが Node 上に存在する場合は pull されません。Always
を指定することでこれを回避できます。
"image": "nginx",
"imagePullPolicy": "Always",
...
ホスト側のデバイスファイルを利用したい場合があります。docker の --device
オプションに相当する機能が k8s で利用できない場合は --privileged
で全デバイスファイルの利用を許可するしかありません。デバイスファイルのうち一部を隠したい場合は、以下のように設定できます。hostPath で FileOrCreate を指定しています。
kubectl run mydeployment --image=nginx --overrides '{
"spec": {
"template": {
"spec": {
"hostname": "yyy",
"containers": [
{
"name": "xxx",
"image": "nginx",
"env": [
{"name":"MYENV", "value":"zzz"}
],
"securityContext": {
"capabilities": {
"add": ["ALL"]
},
"privileged": true
},
"volumeMounts": [
{
"mountPath": "/dev/fuse",
"name": "devfuse"
},
{
"mountPath": "/dev/port",
"name": "devport"
}
]
}
],
"volumes": [
{
"name": "devfuse",
"hostPath": {
"path": "/dev/fuse"
}
},
{
"name": "devport",
"hostPath": {
"path": "/dev/shm/mydevport_mydeployment",
"type": "FileOrCreate"
}
}
]
}
}
}
}'
Pod とサービスを作成します。
kubectl run mydeployment --image=nginx
kubectl expose deployment/mydeployment --type="NodePort" --port 80
-l
で key=value
形式のラベルに合致する Pod およびサービスを選択できます。
kubectl get deployments -l run=mydeployment
kubectl get pods -l run=mydeployment
kubectl get services -l run=mydeployment
追加のラベルを付与するためには以下のようにします。
kubectl label pod mydeployment-65b9f8795d-f67s7 mykey=myvalue
kubectl get pods -l mykey=myvalue
ラベルを指定した削除も行えます。
kubectl delete service -l run=mydeployment
kubectl delete deployments -l run=mydeployment
Pod を作成します。
kubectl run mydeployment --image=nginx
ファイルを転送します。-c
でコンテナを指定できます。
echo 'hello' > /tmp/hello.txt
kubectl cp /tmp/hello.txt mydeployment-65b9f8795d-z7b9d:/tmp/
kubectl exec mydeployment-65b9f8795d-z7b9d -- cat /tmp/hello.txt
ディレクトリも転送できます。
mkdir /tmp/mydir
echo 'hello' > /tmp/mydir/hello1.txt
echo 'hello' > /tmp/mydir/hello2.txt
kubectl cp /tmp/mydir mydeployment-65b9f8795d-z7b9d:/tmp/
kubectl exec mydeployment-65b9f8795d-z7b9d -- ls /tmp/mydir/
取得は以下のようになります。
mkdir /tmp/mydir2
kubectl cp mydeployment-65b9f8795d-z7b9d:/tmp/mydir /tmp/mydir2
Pod を作成します。
kubectl run mydeployment --image=nginx
Pod 内の 80 番を localhost の 8080 で Listen するようにポート転送します。
kubectl port-forward mydeployment-65b9f8795d-d7n66 8080:80
curl localhost:8080
デプロイ設定に対しても実行できます。
kubectl port-forward deployment/mydeployment 8080:80
Node マシン
kubectl top node
kubectl top node debian1
Pod 毎
kubectl top pod -l run=mydeployment
Pod 内のコンテナはネットワークを共有するため、お互いのサービスに localhost:xxxx でアクセスできます。異なる Pod 内のコンテナとの通信については、Rancher の場合、各 Pod に割り当てられた Rancher managed IP を指定して通信できます。既定では managed
ネットワーク 10.42.0.0/16
から各 Pod に ip が割り当てられます。
kubectl exec -it mydeployment-65b9f8795d-5qwtf -- /bin/bash
apt update
apt install iproute2
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
3: eth0@if181: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 0a:a4:0d:10:28:38 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.42.0.84/32 scope global eth0
valid_lft forever preferred_lft forever
また別の方法として kubectl expose
でサービスを作成し、サービスに ClusterIP
を割り当てることで、背後の Pod にアクセスをプロキシして通信することもできます。Rancher の場合は既定では 10.43.0.0/16
が指定できます。
kubectl expose deployment/mydeployment --cluster-ip 10.43.0.123 --port=8080 --container-port=80
kubectl get services
curl 10.43.0.123:8080
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 24m
mydeployment ClusterIP 10.43.0.123 <none> 8080/TCP 23m
以下のコマンドでテンプレートを作成できます。
kubectl expose deployment/mydeployment --type="NodePort" --port 80 --cluster-ip 10.43.0.101 --port=22,80,5900 --dry-run -o yaml
出力された YAML を編集して nodePort
の設定を追加できます。
...
spec:
...
ports:
- name: ssh
port: 22
protocol: TCP
targetPort: 22
nodePort: 30022
...
expose
ではなく create
を利用してサービスを作成します。
kubectl create -f /path/to/my.yaml
新規作成
kubectl create -f nginx.yaml
kubectl create -f configs/
再作成
kubectl replace -f nginx.yaml
削除
kubectl delete -f nginx.yaml -f redis.yaml
一覧取得
kubectl get namespaces
YAML から作成
apiVersion: v1
kind: Namespace
metadata:
name: mynamespace
kubectl explain pods
時系列順にクラスタ内でのイベントを表示できます。デバッグ等で役立ちます。
kubectl get events --sort-by=.metadata.creationTimestamp
v1.12 以上の kubectl で利用できるプラグイン機能を利用すると、実行ファイルをサブコマンドとして登録できます。パスの通ったディレクトリに kubectl-
で始まるファイルを設置するだけで利用できます。
kubectl-xxx-yyy
#!/bin/bash
echo ok
パスの通ったディレクトリに設置
chmod +x kubectl-xxx-yyy
sudo mv kubectl-xxx-yyy /usr/local/bin/
実行例
$ kubectl xxx yyy
ok
プラグインの確認
$ kubectl plugin list
The following kubectl-compatible plugins are available:
/usr/local/bin/kubectl-xxx-yyy
xxxx
というホスト名の Node で起動させたい場合は kubernetes.io/hostname
を設定します。
apiVersion: v1
kind: Pod
metadata:
labels:
run: mypod
name: mypod
spec:
containers:
- image: nginx
name: nginx
restartPolicy: Never
nodeSelector:
kubernetes.io/hostname: xxxx
apiVersion: v1
kind: Pod
metadata:
labels:
run: mypod
name: mypod
spec:
containers:
- image: nginx
name: nginx
resources:
requests:
cpu: 0.0
memory: 1024Mi
limits:
cpu: 1.0
memory: 1024Mi
restartPolicy: Never
正しく設定されていることを確認します。
$ kubectl describe pods | grep Limits -A5
Limits:
cpu: 1
memory: 1Gi
Requests:
cpu: 0
memory: 1Gi
要求したリソースを持つ Node が存在しない場合はスケジューリングに失敗します。
$ kubectl get events --sort-by=.metadata.creationTimestamp
...
Warning FailedScheduling default-scheduler 0/1 nodes are available: 1 Insufficient memory.
clusterIP を None に設定した Service を作成して Pod に向けることで、Service 名が Pod の IP に解決されるような DNS レコードを登録できます。このような Service を Headless service とよびます。Headless service の ports には仮のものを設定します。
apiVersion: v1
kind: Pod
metadata:
labels:
run: mypod
name: mypod
spec:
containers:
- image: nginx
name: nginx
restartPolicy: Never
---
apiVersion: v1
kind: Service
metadata:
labels:
run: myheadless
name: myheadless
spec:
clusterIP: None
selector:
run: mypod
ports:
- name: myheadless
port: 1
Headless service 名で Pod の IP が取得できていることが分かります。Headless service 名を Pod 名と同じにすることもできます。
kubectl -it exec mypod /bin/bash
apt update && apt install dnsutils
root@mypod:/# dig +short +search myheadless
10.42.0.70
root@mypod:/# dig +short +search mypod | wc -l
0
root@mypod:/# cat /etc/hosts | grep 10.
10.42.0.70 mypod
ConfigMap を利用すると、設定ファイルをコンテナ内にマウントできます。
apiVersion: v1
kind: ConfigMap
metadata:
name: myconfig
data:
myconfig.yml: |
xxx: 123
yyy: 123
---
apiVersion: v1
kind: Pod
metadata:
labels:
run: mypod
name: mypod
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: config-volume
mountPath: /mnt/etc/
volumes:
- name: config-volume
configMap:
name: myconfig
restartPolicy: Never
確認
$ kubectl -it exec mypod /bin/bash
root@mypod:/# cat /mnt/etc/myconfig.yml
xxx: 123
yyy: 123
hostAliases
を設定することでコンテナ内の /etc/hosts
にエントリを追加できます。
apiVersion: v1
kind: Pod
metadata:
labels:
run: mypod
name: mypod
spec:
containers:
- image: nginx
name: nginx
restartPolicy: Never
hostAliases:
- ip: 1.2.3.4
hostnames:
- xxx.example.com
- yyy.example.com
設定の確認
$ kubectl -it exec mypod -- cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.42.0.76 mypod
# Entries added by HostAliases.
1.2.3.4 xxx.example.com
1.2.3.4 yyy.example.com