ビビリフクロウの足跡

いつもお世話になっているインターネットへの恩返し

kubeadmを用いたKubernetes HAクラスタ on AWS(v.1.11対応版)

前の記事を公開してすぐにv.1.11が出てしまい、構築方法が変わったので、泣きながら再検証...

構成

  • OS: CentOS7
  • サーバ群
    • Master + etcd
      • 3台(aws-k8s-master[01-03])
    • Node
      • 3台(aws-k8s-node[01-03])

前提

  • 作業ユーザはroot

手順

NLBの作成(AWS ManagementConsole等)

(6443/TCPのリスナを作成、aws-k8s-master[01-03]のIPをターゲットグループに登録する)

Dockerのインストール(aws-k8s-master[01-03]、aws-k8s-node[01-03])

yum install -y docker
systemctl enable docker && systemctl start docker

kubeadm、kubelet、kubectlのインストール(aws-k8s-master[01-03]、aws-k8s-node[01-03])

cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
setenforce 0
yum install -y kubelet kubeadm kubectl
systemctl enable kubelet && systemctl start kubelet
cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

最初のMasterサーバの構築(aws-k8s-master01)

cat <<EOF > kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
apiServerCertSANs:
- "<NLBのDNS名>"
api:
  controlPlaneEndpoint: "<NLBのDNS名>:6443"
etcd:
  local:
    extraArgs:
      listen-client-urls: "https://127.0.0.1:2379,https://<aws-k8s-master01のIP>:2379"
      advertise-client-urls: "https://<aws-k8s-master01のIP>:2379"
      listen-peer-urls: "https://<aws-k8s-master01のIP>:2380"
      initial-advertise-peer-urls: "https://<aws-k8s-master01のIP>:2380"
      initial-cluster: "aws-k8s-master01=https://<aws-k8s-master01のIP>:2380"
    serverCertSANs:
      - aws-k8s-master01
      - <aws-k8s-master01のIP>
    peerCertSANs:
      - aws-k8s-master01
      - <aws-k8s-master01のIP>
networking:
  podSubnet: "192.168.0.0/16"
EOF
kubeadm init --config kubeadm-config.yaml
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

SSHキーペアの作成(aws-k8s-master02, aws-k8s-master03)

ssh-keygen -t rsa -b 4096 -C ""
cat ~/.ssh/id_rsa.pub
# 出力された内容をaws-k8s-master01の/root/.ssh/authorized_keysのエントリを追加する

証明書群、設定ファイル群の配布(aws-k8s-master02、aws-k8s-master03)

mkdir -p /etc/kubernetes/pki/etcd
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/ca.crt /etc/kubernetes/pki/ca.crt
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/ca.key /etc/kubernetes/pki/ca.key
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/sa.key /etc/kubernetes/pki/sa.key
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/sa.pub /etc/kubernetes/pki/sa.pub
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/front-proxy-ca.crt /etc/kubernetes/pki/front-proxy-ca.crt
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/front-proxy-ca.key /etc/kubernetes/pki/front-proxy-ca.key
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/etcd/ca.crt /etc/kubernetes/pki/etcd/ca.crt
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/etcd/ca.key /etc/kubernetes/pki/etcd/ca.key
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/admin.conf /etc/kubernetes/admin.conf

2台目のMasterサーバの構築(aws-k8s-master02)

cat <<EOF > kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
apiServerCertSANs:
- "<NLBのDNS名>"
api:
  controlPlaneEndpoint: "<NLBのDNS名>:6443"
etcd:
  local:
    extraArgs:
      listen-client-urls: "https://127.0.0.1:2379,https://<aws-k8s-master02のIP>:2379"
      advertise-client-urls: "https://<aws-k8s-master02のIP>:2379"
      listen-peer-urls: "https://<aws-k8s-master02のIP>:2380"
      initial-advertise-peer-urls: "https://<aws-k8s-master02のIP>:2380"
      initial-cluster: "aws-k8s-master01=https://<aws-k8s-master01のIP>:2380,aws-k8s-master02=https://<aws-k8s-master02のIP>:2380"
      initial-cluster-state: existing
    serverCertSANs:
      - aws-k8s-master02
      - <aws-k8s-master02のIP>
    peerCertSANs:
      - aws-k8s-master02
      - <aws-k8s-master02のIP>
networking:
  podSubnet: "192.168.0.0/16"
EOF
kubeadm alpha phase certs all --config kubeadm-config.yaml
kubeadm alpha phase kubelet config write-to-disk --config kubeadm-config.yaml
kubeadm alpha phase kubelet write-env-file --config kubeadm-config.yaml
kubeadm alpha phase kubeconfig kubelet --config kubeadm-config.yaml
systemctl start kubelet
CP0_IP=<aws-k8s-master01のIP>
CP0_HOSTNAME=aws-k8s-master01
CP1_IP=<aws-k8s-master02のIP>
CP1_HOSTNAME=aws-k8s-master02

KUBECONFIG=/etc/kubernetes/admin.conf kubectl exec -n kube-system etcd-${CP0_HOSTNAME} -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file /etc/kubernetes/pki/etcd/peer.key --endpoints=https://${CP0_IP}:2379 member add ${CP1_HOSTNAME} https://${CP1_IP}:2380
kubeadm alpha phase etcd local --config kubeadm-config.yaml
kubeadm alpha phase kubeconfig all --config kubeadm-config.yaml
kubeadm alpha phase controlplane all --config kubeadm-config.yaml
kubeadm alpha phase mark-master --config kubeadm-config.yaml

3台目のMasterサーバの構築(aws-k8s-master03)

cat <<EOF > kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
apiServerCertSANs:
- "<NLBのDNS名>"
api:
  controlPlaneEndpoint: "<NLBのDNS名>:6443"
etcd:
  local:
    extraArgs:
      listen-client-urls: "https://127.0.0.1:2379,https://<aws-k8s-master03のIP>:2379"
      advertise-client-urls: "https://<aws-k8s-master03のIP>:2379"
      listen-peer-urls: "https://<aws-k8s-master03のIP>:2380"
      initial-advertise-peer-urls: "https://<aws-k8s-master03のIP>:2380"
      initial-cluster: "aws-k8s-master01=https://<aws-k8s-master01のIP>:2380,aws-k8s-master02=https://<aws-k8s-master02のIP>:2380,aws-k8s-master03=https://<aws-k8s-master03のIP>:2380"
      initial-cluster-state: existing
    serverCertSANs:
      - aws-k8s-master03
      - <aws-k8s-master03のIP>
    peerCertSANs:
      - aws-k8s-master03
      - <aws-k8s-master03のIP>
networking:
  podSubnet: "192.168.0.0/16"
EOF
kubeadm alpha phase certs all --config kubeadm-config.yaml
kubeadm alpha phase kubelet config write-to-disk --config kubeadm-config.yaml
kubeadm alpha phase kubelet write-env-file --config kubeadm-config.yaml
kubeadm alpha phase kubeconfig kubelet --config kubeadm-config.yaml
systemctl start kubelet
CP0_IP=<aws-k8s-master01のIP>
CP0_HOSTNAME=aws-k8s-master01
CP2_IP=<aws-k8s-master03のIP>
CP2_HOSTNAME=aws-k8s-master03

KUBECONFIG=/etc/kubernetes/admin.conf kubectl exec -n kube-system etcd-${CP0_HOSTNAME} -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file /etc/kubernetes/pki/etcd/peer.key --endpoints=https://${CP0_IP}:2379 member add ${CP2_HOSTNAME} https://${CP2_IP}:2380
kubeadm alpha phase etcd local --config kubeadm-config.yaml
kubeadm alpha phase kubeconfig all --config kubeadm-config.yaml
kubeadm alpha phase controlplane all --config kubeadm-config.yaml
kubeadm alpha phase mark-master --config kubeadm-config.yaml

flannelのインストール(aws-k8s-master01)

curl -O https://raw.githubusercontent.com/coreos/flannel/v0.10.0/Documentation/kube-flannel.yml
sed -i 's/10.244.0.0/192.168.0.0/g' kube-flannel.yml
kubectl apply -f kube-flannel.yml

Nodeのクラスタ参加(aws-k8s-node[01-03])

kubeadm join --token <token> <NLBのDNS名>:6443 --discovery-token-ca-cert-hash sha256:<hash>
# <token>、<hash>はkubeadm initをaws-k8s-master01で実行した際に表示されたものを使用

kubeadmを用いたKubernetes HAクラスタ on AWS

AWS上でkubeadmを使い、Kubernetes HAクラスタを作ってみたときのメモです。

構成

  • OS: CentOS7
  • サーバ群
    • Master + etcd
      • 3台(aws-k8s-master[01-03])
    • Node
      • 3台(aws-k8s-node[01-03])

前提

  • 作業ユーザはroot

手順

Dockerのインストール(aws-k8s-master[01-03]、aws-k8s-node[01-03])

yum install -y docker
systemctl enable docker && systemctl start docker

kubeadm、kubelet、kubectlのインストール(aws-k8s-master[01-03]、aws-k8s-node[01-03])

cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
setenforce 0
yum install -y kubelet kubeadm kubectl
systemctl enable kubelet && systemctl start kubelet
cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

cfssl、cfssljsonのインストール(aws-k8s-master[01-03])

curl -o /usr/local/bin/cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
curl -o /usr/local/bin/cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
chmod +x /usr/local/bin/cfssl*

etcdサーバのためのCA証明書作成(aws-k8s-master01)

mkdir -p /etc/etcd/pki
cd /etc/etcd/pki
cat >ca-config.json <<EOF
{
   "signing": {
       "default": {
           "expiry": "175200h"
       },
       "profiles": {
           "server": {
               "expiry": "175200h",
               "usages": [
                   "signing",
                   "key encipherment",
                   "server auth",
                   "client auth"
               ]
           },
           "client": {
               "expiry": "175200h",
               "usages": [
                   "signing",
                   "key encipherment",
                   "client auth"
               ]
           },
           "peer": {
               "expiry": "175200h",
               "usages": [
                   "signing",
                   "key encipherment",
                   "server auth",
                   "client auth"
               ]
           }
       }
   }
}
EOF
# 証明書の有効期限は20年
cat >ca-csr.json <<EOF
{
   "CN": "etcd",
   "key": {
       "algo": "rsa",
       "size": 2048
   }
}
EOF
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -

etcdのクライアント証明書の作成(aws-k8s-master01)

cat >client.json <<EOF
{
  "CN": "client",
  "key": {
      "algo": "ecdsa",
      "size": 256
  }
}
EOF
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client

環境変数のエクスポート(aws-k8s-master[01-03])

export PEER_NAME=$(hostname)
export PRIVATE_IP=$(ip addr show eth0 | grep -Po 'inet \K[\d.]+')

SSHキーペアの作成(aws-k8s-master02, aws-k8s-master03)

ssh-keygen -t rsa -b 4096 -C ""
cat ~/.ssh/id_rsa.pub
# 出力された内容をaws-k8s-master01の/root/.ssh/authorized_keysのエントリを追加する

CA証明書とクライアント証明書の配布(aws-k8s-master02, aws-k8s-master03)

mkdir -p /etc/etcd/pki
cd /etc/etcd/pki
scp root@<aws-k8s-master01のIP>:/etc/etcd/pki/ca.pem .
scp root@<aws-k8s-master01のIP>:/etc/etcd/pki/ca-key.pem .
scp root@<aws-k8s-master01のIP>:/etc/etcd/pki/client.pem .
scp root@<aws-k8s-master01のIP>:/etc/etcd/pki/client-key.pem .
scp root@<aws-k8s-master01のIP>:/etc/etcd/pki/ca-config.json .

etcdサーバ証明書とpeer証明書の作成(aws-k8s-master[01-03])

cfssl print-defaults csr > config.json
sed -i '0,/CN/{s/example\.net/'"$PEER_NAME"'/}' config.json
sed -i 's/www\.example\.net/'"$PRIVATE_IP"'/' config.json
sed -i 's/example\.net/'"$PEER_NAME"'/' config.json
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server config.json | cfssljson -bare server
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=peer config.json | cfssljson -bare peer

etcdサーバのsystemd登録と起動設定(aws-k8s-master[01-03])

export ETCD_VERSION="v3.1.12"
curl -sSL https://github.com/coreos/etcd/releases/download/${ETCD_VERSION}/etcd-${ETCD_VERSION}-linux-amd64.tar.gz | tar -xzv --strip-components=1 -C /usr/local/bin/
touch /etc/etcd.env
echo "PEER_NAME=${PEER_NAME}" >> /etc/etcd.env
echo "PRIVATE_IP=${PRIVATE_IP}" >> /etc/etcd.env
cat >/etc/systemd/system/etcd.service <<EOF
[Unit]
Description=etcd
Documentation=https://github.com/coreos/etcd
Conflicts=etcd.service
Conflicts=etcd2.service

[Service]
EnvironmentFile=/etc/etcd.env
Type=notify
Restart=always
RestartSec=5s
LimitNOFILE=40000
TimeoutStartSec=0

ExecStart=/usr/local/bin/etcd --name <name> --data-dir /var/lib/etcd --listen-client-urls https://<my_ip>:2379 --advertise-client-urls https://<my_ip>:2379 --listen-peer-urls https://<my_ip>:2380 --initial-advertise-peer-urls https://<my_ip>:2380 --cert-file=/etc/etcd/pki/server.pem --key-file=/etc/etcd/pki/server-key.pem --client-cert-auth --trusted-ca-file=/etc/etcd/pki/ca.pem --peer-cert-file=/etc/etcd/pki/peer.pem --peer-key-file=/etc/etcd/pki/peer-key.pem --peer-client-cert-auth --peer-trusted-ca-file=/etc/etcd/pki/ca.pem --initial-cluster aws-k8s-master01=https://<aws-k8s-master01のIP>:2380,aws-k8s-master02=https://<aws-k8s-master02のIP>:2380,aws-k8s-master03=https://<aws-k8s-master03のIP>:2380 --initial-cluster-token my-etcd-token --initial-cluster-state new
# <name>にはaws-k8s-master[01-03]が入る

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable etcd
systemctl start etcd

NLBの作成(AWS ManagementConsole等)

(6443/TCPのリスナを作成、aws-k8s-master01のインスタンスをターゲットグループに登録する)
(NLB作成後、DNS名にpingを発行し、NLBのIPを確認する)

kubeadmの実行(aws-k8s-master01)

cat >config.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
etcd:
  endpoints:
  - https://<aws-k8s-master01のIP>:2379
  - https://<aws-k8s-master02のIP>:2379
  - https://<aws-k8s-master03のIP>:2379
  caFile: /etc/etcd/pki/ca.pem
  certFile: /etc/etcd/pki/client.pem
  keyFile: /etc/etcd/pki/client-key.pem
networking:
  podSubnet: 192.168.0.0/16
apiServerCertSANs:
- <NLBのIP>
- <my_ip>
apiServerExtraArgs:
  apiserver-count: "3"
EOF
kubeadm init --config=config.yaml
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

kubeadmの実行(aws-k8s-master02,aws-k8s-master03)

mkdir /etc/kubernetes/pki
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/* /etc/kubernetes/pki
rm -f /etc/kubernetes/pki/apiserver*
cat >config.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
etcd:
  endpoints:
  - https://<aws-k8s-master01のIP>:2379
  - https://<aws-k8s-master02のIP>:2379
  - https://<aws-k8s-master03のIP>:2379
  caFile: /etc/etcd/pki/ca.pem
  certFile: /etc/etcd/pki/client.pem
  keyFile: /etc/etcd/pki/client-key.pem
networking:
  podSubnet: 192.168.0.0/16
apiServerCertSANs:
- <NLBのIP>
- <my_ip>
apiServerExtraArgs:
  apiserver-count: "3"
EOF
kubeadm init --config=config.yaml
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

NLBへ残りのMasterを追加(AWS ManagementConsole等)

(NLBのターゲットグループにaws-k8s-master02、aws-k8s-master03を追加)

calicoのインストール(aws-k8s-master01)

kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml
kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml

Nodeのクラスタ参加(aws-k8s-node[01-03])

kubeadm join --token <token> <aws-k8s-master01のIP>:6443 --discovery-token-ca-cert-hash sha256:<hash>
# <token>、<hash>はkubeadm initをaws-k8s-master01で実行した際に表示されたものを使用

kube-proxyの再設定(aws-k8s-master01)

kubectl get configmap -n kube-system kube-proxy -o yaml > kube-proxy-cm.yaml
sed -i 's#server:.*#server: https://<NLBのIP>:6443#g' kube-proxy-cm.yaml
kubectl apply -f kube-proxy-cm.yaml --force
kubectl delete pod -n kube-system -l k8s-app=kube-proxy

kubeletの再設定(aws-k8s-node[01-03])

sed -i 's#server:.*#server: https://<NLBのIP>:6443#g' /etc/kubernetes/kubelet.conf
systemctl restart kubelet

gVisorを試してみた

Googleが開発したgVisorを試して、簡単に性能調査してみましたので、メモとして残しておきます。

環境

gVisorのインストール手順

  • Dockerのインストール
sudo apt-get update
sudo apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
sudo apt-get update
sudo apt-get install -y docker-ce
  • gVisor(runsc)のインストール
wget https://storage.googleapis.com/gvisor/releases/nightly/latest/runsc
chmod +x runsc
sudo mv runsc /usr/local/bin
sudo sh -c 'cat <<EOF > /etc/docker/daemon.json
{
    "runtimes": {
        "runsc": {
            "path": "/usr/local/bin/runsc"
        }
    }
}
EOF'
sudo systemctl restart docker
  • コンテナを起動してみる
sudo docker run --runtime=runc hello-world
sudo docker run --runtime=runsc hello-world

性能調査

簡単なスクリプトでコンテナ起動と削除のスピードを評価します。

cat <<EOF > runc-test.sh
for i in \$(seq 1 \$1) ; do
    sudo docker run --runtime=runc --name gvisor-test hello-world > /dev/null 2>&1
    sudo docker rm gvisor-test > /dev/null 2>&1
done
EOF
chmod +x runc-test.sh

cat <<EOF > runsc-test.sh
for i in \$(seq 1 \$1) ; do
    sudo docker run --runtime=runsc --name gvisor-test hello-world > /dev/null 2>&1
    sudo docker rm gvisor-test > /dev/null 2>&1
done
EOF
chmod +x runsc-test.sh

スクリプトの使い方としては./runc-test.sh <起動および削除回数>./runsc-test.sh <起動および削除回数>とします。試しに両方100回ずつ起動・削除を繰り返すと、以下のような結果となりました。

ubuntu@gvisor-01:~$ time ./runc-test.sh 100

real    1m20.991s
user    0m14.948s
sys     0m1.768s
ubuntu@gvisor-01:~$ time ./runsc-test.sh 100

real    1m13.219s
user    0m15.100s
sys     0m1.748s

起動・停止だけではruncとあまり差はみられませんね。むしろrunscの方が速い? ネットワークトラフィックの差をとるとruncに軍配が上がるみたいですが。。。

speakerdeck.com


追記(2018/06/07) 気になってDisk書き込み速度も調べてみましたが、逆転現象が起きましたw

  • runc
root@4aee2e622884:/# dd if=/dev/zero of=/tmp/write.tmp ibs=1M obs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 4.17698 s, 257 MB/s
  • runsc
root@ce7e3926f52e:/# dd if=/dev/zero of=/tmp/write.tmp ibs=1M obs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.853382 s, 1.3 GB/s

Rancher 2.0で複数のKubernetes環境を管理してみる

2018年4月末にコンテナ管理プラットフォームであるRancherのバージョン2.0がGA(General Availability)となりました。対応するコンテナオーケストレーションツールをKubernetesに絞り、オンプレ/クラウド問わずKubernetesクラスタをマネジメントするOSSに昇華されているみたいです。今回はこのRancher 2.0を使って

  • 新しいKubernetesクラスタの作成
  • 既存のKubernetesクラスタのインポート
  • Helmでアプリケーションをデプロイ

というところまでをやってみたいと思います。

環境

今回は以下の環境で試用してみます。

  • OS
  • Rancher
    • Ver: v2.0.2
  • インポートするKubernetes
    • Ver: 1.10.0
  • RKE(Rancher Kubernetes Engine)のKubernetes
    • Ver: 1.10.1
  • 構成サーバ群
    • Rancherサーバ : rancher-server-01
    • RKE k8sマスター: rancher-k8s-master-01
    • RKE k8sノード#1: rancher-k8s-node-01
    • RKE k8sノード#2: rancher-k8s-node-02
    • 自前k8sマスター: tst-k8s-master01
    • 自前k8sノード#1: tst-k8s-node01
    • 自前k8sノード#2: tst-k8s-node02

事前準備

今回は検証なので、ポートは全開放でいきます。以下のコマンドを全ノードで叩きます。

firewall-cmd --set-default-zone=trusted

次に全ノードにDockerをインストールして、サービスを起動します。

yum -y install docker
systemctl enable docker
systemctl start docker

この時点でインポートする自前k8sを構築しておきます。注意点としては、kube-dnsIngress Controllerを忘れずにデプロイしておきます。

Rancherサーバの構築

Rancherサーバを構築していきますが、難しいことはありません。以下のコマンドを叩くだけです。

mkdir /var/lib/rancher
docker run -d --restart=unless-stopped -p 80:80 -p 443:443 -v /var/lib/rancher:/var/lib/rancher:Z rancher/rancher:v2.0.2

ここで/var/lib/rancherはRancherサーバの永続ボリュームとして使うディレクトリなので、お好みで調整してください。

またCentOSをはじめとしたRHEL系OSではSELinuxが有効になっているかと思いますので、-vオプションの末尾に:Zをつけて適切なラベリングを行うようにしておきます。

Rancherサーバが起動したら、ウェブブラウザからRancherサーバにログインします。するとまずadminユーザのパスワードを作成するよう求められるので、パスワードを入力して次に進みます。

f:id:bbrfkr:20180527133404p:plain

RancherサーバのURLはアクセスした際のURLがデフォルトで表示されていますので、そのままで次に進みます。

f:id:bbrfkr:20180527133617p:plain

すると以下のような画面が表示されます。クラスタを登録する前にHelm(k8sのパッケージマネージャ)を有効化しておきましょう。「Catalog」を選択します。

f:id:bbrfkr:20180527133935p:plain

「Helm Stable」のスイッチを「Enabled」にします。

f:id:bbrfkr:20180527134026p:plain

これでHelmの有効化は完了です。

新しいKubernetesクラスタの作成

次にクラスタを登録していきましょう。まずはRKEクラスタを作成・登録します。「Clusters」を選択し、「Add Cluster」をクリックします。

f:id:bbrfkr:20180527134301p:plain

すると以下のような画面になります。今回は一般的な仮想マシンからk8sクラスタを作成しますので、「CUSTOM」を選択し、「Next」をクリックします。

f:id:bbrfkr:20180527134558p:plain

次の画面ではRKE k8sクラスタを作成するのに必要なコマンドが表示されます。k8sマスターを登録をするには「Node Role」の

  • etcd
  • Control
  • Worker

にチェックを入れ、記載されているコマンドを実行します。が、SELinuxが有効な環境に対してはこのままではダメで、-vオプションの末尾に:Zをつけるのを忘れないようにしましょう。

f:id:bbrfkr:20180527135124p:plain

同様にk8sノードを登録するには「Node Role」の

  • Worker

にチェックを入れ、記載されているコマンドを実行します。-vオプションの末尾に:Zをつけるのを忘れずに。

それぞれのサーバでコマンド実行が終わったら「Done」をクリックします。そうするとRKE k8sクラスタの作成が始まります。

f:id:bbrfkr:20180527135420p:plain

既存のKubernetesクラスタのインポート

RKE k8sクラスタの作成を待っている間に既存k8sクラスタをRancherサーバにインポートしましょう。「Clusters」を選択して「Add Cluster」をクリックし、今度は「IMPORT」を選択します。その後「Next」をクリックしましょう。

f:id:bbrfkr:20180527135816p:plain

すると既存k8sクラスタをRancherサーバに登録するためのコマンドが発行されますので、k8sマスター上で二番目のコマンドを叩きます。叩いたら「Done」をクリックします。

f:id:bbrfkr:20180527140041p:plain

戻った画面で「State」の値が「Active」になっていればRKE k8sクラスタの作成・登録、既存k8sクラスタの登録は完了です。

f:id:bbrfkr:20180527140407p:plain

Helmでアプリケーションをデプロイ

Rancherに二つのk8sクラスタが登録されたので、それぞれのクラスタ上にアプリケーションをデプロイしてみましょう。まずはRKE k8sクラスタ上にデプロイしてみます。「Global」から「Cluster: rke-cluster-01 > Default」をクリックします。

f:id:bbrfkr:20180527140648p:plain

「Catalog Apps」を選択し、「Launch」をクリックします。

f:id:bbrfkr:20180527140943p:plain

表示されたカタログ群から「dokuwiki」を選択します。

f:id:bbrfkr:20180527141051p:plain

今回はPersistent Volumeをまだサポートしていないクラスタなので、アプリケーションのデプロイでPersistent Volumeを使わない設定にします。またデフォルトではLoadBalancerタイプのServiceが作られるので、これをClusterIPに変更します。下のほうに進んで「Add Answer」を2回クリックし、以下のKey-Valueを入力します。その後「Launch」をクリックします。

  • serviceType = ClusterIP
  • persistence.enabled = false

f:id:bbrfkr:20180527141526p:plain

しばらくすると以下のようにゲージが緑になります。この状態になれば必要なPodは上がっている状態です。

f:id:bbrfkr:20180527141716p:plain

では、Ingressを作成して外部からdokuwikiにアクセスできるようにしましょう。「Workloads」を選択し「Load Balancing」タブを選んでから「Add Ingress」をクリックします。

f:id:bbrfkr:20180527152045p:plain

Ingressを作成する際のパラメータは以下の通りにします。

  • Name: (任意の名前)
  • Namespace: (作成されたNamespace)
  • Rules
    • Automatically generate a .xip.io hostname
    • Target: (作成されたService)
    • Port: 80

f:id:bbrfkr:20180527152726p:plain

以上、入力したら下の「Save」をクリックします。少し経つと以下の通りURLが発行されるので、アクセスしてみましょう。dokuwikiのホームページが表示されます!

f:id:bbrfkr:20180527152913p:plain

f:id:bbrfkr:20180527152939p:plain

次は既存k8sクラスタ上にアプリケーションをデプロイしてみます。「Global」から「Cluster: my-cluster-01 > Default」をクリックします。

f:id:bbrfkr:20180527153218p:plain

「Catalog Apps」を選択し、「Launch」をクリックします。

f:id:bbrfkr:20180527153309p:plain

表示されたカタログ群から「zeppelin」を選択します。

f:id:bbrfkr:20180527153401p:plain

このカタログではデフォルトのCPU・メモリリソースを最低2000millicore、4096MiB確保するように設定されているので、今回構築したクラスタで展開できる値に修正します。「Add Answer」を2回クリックし、以下のKey-Valueを入力します。その後「Launch」をクリックします。

  • zeppelin.resources.limits.cpu: 1000m
  • zeppelin.resources.limits.meory: 2048Mi

f:id:bbrfkr:20180527153631p:plain

ゲージが緑になるまで待ちましょう。

f:id:bbrfkr:20180527153739p:plain

dokuwikiと同様、Ingressを作っていきます。「Workloads」を選択し「Load Balancing」タブを選んでから「Add Ingress」をクリックします。

f:id:bbrfkr:20180527153856p:plain

Ingressを作成する際のパラメータは以下の通りにします。

  • Name: (任意の名前)
  • Namespace: (作成されたNamespace)
  • Rules
    • Specify a hostname use
      • Request Host: (Ingress Controllerに対応した名前)
    • Target: (作成されたService)
    • Port: 8080

f:id:bbrfkr:20180527154002p:plain

以上、入力したら下の「Save」をクリックします。表示されたURLにアクセスしてみましょう。zeppelinのホームページが表示されます!

f:id:bbrfkr:20180527154255p:plain

f:id:bbrfkr:20180527154318p:plain

API Aggregationを有効化してPrometheusのメトリクスからHorizonPodAutoscalerでPodのオートスケールをする

やたら長いタイトルですが…

KubernetesではAPI Aggregationといって、Custom Metrics API Serverを用意しkube-apiserverと連携することでAPIを拡張することができます。例えば今回のタイトルにもある通り、Prometheusが取得したメトリクスをAPIから取得できるようにすることができます。この記事では以下を可能とするKubernetesの設定を説明します。

  • API Aggregationの有効化
  • Prometheus用のCustom Metrics API Serverの構築とkube-apiserverとの連携

API Aggregationの有効化

API Aggregationを有効化するにはkube-apiserverkube-controller-managerに設定が必要になります。

まず、kube-controller-managerに以下のオプションを設定して再起動します。

--horizontal-pod-autoscaler-use-rest-clients=true

次にkube-apiserverの認証方式において、x509証明書認証を有効化するために、CA証明書を用意します。CA証明書のファイルパスを/etc/kubernetes/certs/ca.crtとします。

さらにkube-apiserverとCustom Metrics API Serverとが連携するために使うクライアント証明書と秘密鍵、そしてクライアント証明書の署名に使ったCAのCA証明書を用意します。それぞれのファイルパスを/etc/kubernetes/certs/proxy-client.crt/etc/kubernetes/certs/proxy-client.key/etc/kubernetes/certs/proxy-ca.crtとします。

その後kube-apiserverに以下のオプションを設定します

--client-ca-file=/etc/kubernetes/certs/ca.crt
--requestheader-client-ca-file=/etc/kubernetes/certs/proxy-ca.crt
--requestheader-allowed-names=''
--requestheader-extra-headers-prefix=X-Remote-Extra-
--requestheader-group-headers=X-Remote-Group
--requestheader-username-headers=X-Remote-User
--proxy-client-cert-file=/etc/kubernetes/certs/proxy-client.crt
--proxy-client-key-file=/etc/kubernetes/certs/proxy-client.key

最後にkube-apiserverの再起動を行うのですが、もし既に--client-ca-fileの値を指定していて今回CA証明書を変更する場合、既にAPIサーバ間連携に使用するConfigMap extension-apiserver-authenticationができてしまっているので、これをあらかじめ削除しておきます。

kubectl -n kube-system delete configmap extension-apiserver-authentication

その後、kube-apiserverを再起動します。

これでAPI Aggregationの設定は完了です。

Prometheus用のCustom Metrics API Serverの構築とkube-apiserverとの連携

Prometheus用のCustom Metrics API Serverを構築し、kube-apiserverにAPIエンドポイントを作成してkube-apiserverとCustom Metrics API Serverとを連携させます。

前提条件として名前空間kube-systemにPrometheusは構築済みであるとします。ここではPrometheusサーバ(port: 9090)のホスト名をprometheus-serverであるとします。

まずはCustom Metrics API Serverを構築するためのKubernetes Manifestをダウンロードします。

git clone https://github.com/DirectXMan12/k8s-prometheus-adapter
cd k8s-prometheus-adapter

ダウンロードしてきたManifestの中にあるPrometheusサーバの指定を変更します。

sed -i 's/prometheus.prom.svc:9090/prometheus-server/g' deploy/manifests/custom-metrics-apiserver-deployment.yaml

また、Manifest内でリソースを作成する名前空間kube-systemに変更します。

sed -i 's/namespace: custom-metrics/namespace: kube-system/g' deploy/manifests/*

さらにPullしてくるCustom Metrics API Serverコンテナイメージのタグを付与します。

sed -i 's/directxman12\/k8s-prometheus-adapter/directxman12\/k8s-prometheus-adapter:advanced-config/g' deploy/manifests/custom-metrics-apiserver-deployment.yaml

次にCustom Metrics API Serverのサーバ証明書秘密鍵を作成します。ファイル名はそれぞれserving.crtserving.keyとします。

作成したサーバ証明書秘密鍵を使用してSecret cm-adapter-serving-certsを作成します。

kubectl -n kube-system create secret generic --from-file=serving.crt=serving.crt --from-file=serving.key=serving.key cm-adapter-serving-certs

最後にCustom Metrics API Serverを構築します。

kubectl create -f deploy/manifests/

構築がうまくいっていれば、APIを叩くと以下のような出力が得られると思います。

kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "custom.metrics.k8s.io/v1beta1",
  "resources": [
    {
      "name": "pods/fs_sector_writes",
      "singularName": "",
      "namespaced": true,
      "kind": "MetricValueList",
      "verbs": [
        "get"
      ]
    },
<中略>
    {
      "name": "jobs.batch/kube_pod_status_phase",
      "singularName": "",
      "namespaced": true,
      "kind": "MetricValueList",
      "verbs": [
        "get"
      ]
    }
  ]
}

PrometheusのメトリクスからPodをオートスケールする

では実際に構築したCustom Metrics API Serverを使ってオートスケールを実現してみましょう。 まずはテスト用にDeployment、Serviceを作ります。

kubectl create deploy httpd --image httpd
kubectl expose deploy httpd --port 80
kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
httpd        ClusterIP   172.16.132.62   <none>        80/TCP    10s
kubernetes   ClusterIP   172.16.0.1      <none>        443/TCP   56m

次にHorizontalPodAutoScalerをDeployment httpdに対して作ります。

cat << EOF > hpa-httpd.yaml
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: httpd
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: httpd
  minReplicas: 1
  maxReplicas: 8
  metrics:
  - type: Pods
    pods:
      metricName: cpu_usage
      targetAverageValue: 1m
EOF
kubectl create -f hpa-httpd.yaml

HPAが作成出来たら、以下のスクリプトを実行してPodに負荷をかけます。

cat << EOF > hpa-test.sh
#!/bin/sh
count=1
while(true) do
  curl http://172.16.132.62 > /dev/null 2>&1 &
  echo access count = \$((count++))
done
EOF
chmod 755 hpa-test.sh
./hpa-test.sh

するとPodが増えていく様が見て取れるはずです。

KubernetesでHorizontal Pod Autoscalerを使えるようにする

KubernetesにおけるHorizontal Pod Autoscaler(HPA)とは、Podの過負荷時(CPU使用量増、メモリ使用量増など)においてPodをオートスケーリングするリソースです。DeploymentやReplicaSetに紐付けて使います。

HPAを使ってオートスケーリングを実現するには、Podの負荷を監視する必要があります。つまり種々のメトリクス(CPU使用率、メモリ使用率など)を取得する必要があります。

Kubernetesでもっとも簡単にメトリクス取得する方法はおそらくHeapsterという監視ソフトウェアを使うことです。Helmを使って簡単にインストールできます。

# helm install stable/heapster --namespace kube-system

Kubernetes v.1.10だと、HPAを実現するデフォルトのメトリクス取得先はAggregation APIになっており、Heapsterのメトリクスを見てくれません。そこでkube-controller-managerの起動オプションに次のオプションを加えます。

--horizontal-pod-autoscaler-use-rest-clients=false

これでHeapsterを使ってオートスケーリングを実現することができます。

Haproxy Ingress Controllerの導入

OpenShiftではおなじみのroute機能を、Kubernetes上でもIngressを使って再現してみたいと思います。

まずはDNSワイルドカードの有効化から。今回は*.ha-kubernetes.internalというアドレスをhaproxyポッドに割り当てます。

DNSサーバの設定

# vi /etc/dnsmasq.conf
address=/ha-kubernetes.internal/192.168.0.89
# systemctl restart dnsmasq

例のごとくマルチマスターKubernetes環境を前提とするので、フロントエンドのロードバランサ(これもhaproxy)に対し80や443ポートにアクセスが来たら、マスターの同一ポートにリクエストを流すように設定します。

フロントエンドロードバランサの設定(haproxy-*)

# vi /etc/haproxy/haproxy.cfg
...
frontend router_http *:80
    default_backend    router_http

frontend router_https *:443
    default_backend    router_https

frontend router_stat *:1936
    default_backend    router_stat
...
backend router_http
    balance   leastconn
    server    master1  kube-master-01.bbrfkr.mydns.jp:80  check
    server    master2  kube-master-02.bbrfkr.mydns.jp:80  check  backup
    server    master3  kube-master-03.bbrfkr.mydns.jp:80  check  backup

backend router_https
    balance   leastconn
    server    master1  kube-master-01.bbrfkr.mydns.jp:443  check
    server    master2  kube-master-02.bbrfkr.mydns.jp:443  check  backup
    server    master3  kube-master-03.bbrfkr.mydns.jp:443  check  backup

backend router_stat
    balance   leastconn
    server    master1  kube-master-01.bbrfkr.mydns.jp:1936  check
    server    master2  kube-master-02.bbrfkr.mydns.jp:1936  check  backup
    server    master3  kube-master-03.bbrfkr.mydns.jp:1936  check  backup
# systemctl restart haproxy

デフォルトでkube-apiserverはnodePortに30000-32767の間しか指定できないので、この縛りを緩くします。

nodeportレンジの変更(kube-master-*)

# sed -i 's/KUBE_API_ARGS="/KUBE_API_ARGS="--service-node-port-range 1-32767 /g' /etc/kubernetes/apiserver
# systemctl restart kube-apiserver

これからが実際のHaproxy Ingress Controllerの設定です。

haproxy ingress controllerの作成

  • デフォルトページ(404)作成
# vi ingress-default-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: ingress-default-backend
  name: ingress-default-backend
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      run: ingress-default-backend
  template:
    metadata:
      labels:
        run: ingress-default-backend
    spec:
      containers:
        - name: ingress-default-backend
          image: gcr.io/google_containers/defaultbackend:1.0
          ports:
          - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  labels:
    run: ingress-default-backend
  name: ingress-default-backend
  namespace: kube-system
spec:
  ports:
  - name: port-1
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    run: ingress-default-backend
# kubectl create -f ingress-default-deployment.yaml
  • controllerのコンフィグ作成
# vi haproxy-configmap.yaml
apiVersion: v1
data:
    dynamic-scaling: "true"
    backend-server-slots-increment: "4"
kind: ConfigMap
metadata:
  name: haproxy-configmap
  namespace: kube-system
# kubectl create -f haproxy-configmap.yaml
  • ingress controllerのデプロイ
# vi haproxy-ingress-deployment.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: haproxy-ingress
  namespace: kube-system

---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: haproxy-ingress-clusterrole
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
        - events
    verbs:
        - create
        - patch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses/status
    verbs:
      - update

---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: haproxy-ingress-role
  namespace: kube-system
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<haproxy>"
      # This has to be adapted if you change either parameter
      # when launching the haproxy-ingress-controller.
      - "ingress-controller-leader-haproxy"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: haproxy-ingress-role-nisa-binding
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: haproxy-ingress-role
subjects:
  - kind: ServiceAccount
    name: haproxy-ingress
    namespace: kube-system

---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: haproxy-ingress-clusterrole-nisa-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: haproxy-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: haproxy-ingress
    namespace: kube-system

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: haproxy-ingress
  name: haproxy-ingress
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      run: haproxy-ingress
  template:
    metadata:
      labels:
        run: haproxy-ingress
    spec:
      containers:
      - name: haproxy-ingress
        image: quay.io/jcmoraisjr/haproxy-ingress
        args:
        - --default-backend-service=kube-system/ingress-default-backend
        - --default-ssl-certificate=kube-system/tls-secret
        - --configmap=$(POD_NAMESPACE)/haproxy-configmap
        - --reload-strategy=native
        ports:
        - name: http
          containerPort: 80
        - name: https
          containerPort: 443
        - name: stat
          containerPort: 1936
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
      serviceAccountName: haproxy-ingress 
# kubectl create -f haproxy-ingress-deployment.yaml
  • ingress controllerの外部公開サービス作成
# vi haproxy-ingress-svc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    run: haproxy-ingress
  name: haproxy-ingress
  namespace: kube-system
spec:
  type:
    NodePort
  ports:
  - name: port-1
    port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 80
  - name: port-2
    port: 443
    protocol: TCP
    targetPort: 443
    nodePort: 443
  - name: port-3
    port: 1936
    protocol: TCP
    targetPort: 1936
    nodePort: 1936
  selector:
    run: haproxy-ingress
# kubectl create -f haproxy-ingress-svc.yaml

ここまでで、Haproxy Ingress Controllerの構成はおしまいです。実際に動作確認をしてみましょう。

動作確認

  • エンドポイントサービスの作成
# vi httpd-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: httpd
  name: httpd
spec:
  replicas: 1
  selector:
    matchLabels:
      run: httpd
  template:
    metadata:
      labels:
        run: httpd
    spec:
      containers:
        - name: httpd
          image: httpd
          ports:
          - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    run: httpd
  name: httpd
  namespace: default
spec:
  ports:
    - name: port-1
      port: 80
      protocol: TCP
      targetPort: 80
  selector:
    run: httpd
# kubectl create -f httpd-deployment.yaml
# vi httpd-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: httpd
spec:
  rules:
  - host: test.ha-kubernetes.internal
    http:
      paths:
      - path: /
        backend:
          serviceName: httpd
          servicePort: 80
# kubectl create -f httpd-ingress.yaml
  • ブラウザでtest.ha-kubernetes.internalにアクセス