ビビリフクロウの足跡

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

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にアクセス

Kubernetes環境にDashboardとDNSサービスをデプロイする

前回作成したマルチマスターKubernetes環境はKubernetesの最小構成なので、今回はDashboardDNSサービスをデプロイしたいと思います。 まずはDashboardのデプロイから。

Dashboardの設定

  • kube-controller-managerの設定で、ServiceAccountがAPIサーバとの通信に使うルートCA証明書を持つように設定する
# sed -i 's@KUBE_CONTROLLER_MANAGER_ARGS="@KUBE_CONTROLLER_MANAGER_ARGS="--root-ca-file=/etc/kubernetes/certs/apiserver.crt @g' /etc/kubernetes/controller-manager
# systemctl restart kube-controller-manager
  • 既存のServiceAccount「default」を削除(自動的に新しいCA証明書を持つServiceAccountが再作成されます)
# kubectl delete sa default
# kubectl delete sa default -n kube-system
# kubectl delete sa default -n kube-public
  • APIサーバ側でsystem:anonymousがNamespace"kube-system"のResource"*"にRW権限を付ける
    • ABACの設定
# vi /etc/kubernetes/abac-policy.json
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"system:anonymous", "namespace": "kube-system", "resource": "*"}}
  • APIサーバの認可機能で、RBACも適用するように設定する。
# sed -i 's/--authorization-mode=ABAC/--authorization-mode=RBAC,ABAC/g' /etc/kubernetes/apiserver
# systemctl restart kube-apiserver
  • manifestを落としてくる
# curl -O https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
  • manifestの修正
    • Dashboard自体のベーシック認証を有効化する
      • オプション指定
...
    spec:
      containers:
      - name: kubernetes-dashboard
        image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.8.3
        ports:
        - containerPort: 8443
          protocol: TCP
        args:
          - --auto-generate-certificates
          - --authentication-mode     # ここを追加
          - basic                     # ここを追加
          # Uncomment the following line to manually specify Kubernetes API server Host
          # If not specified, Dashboard will attempt to auto discover the API server and connect
          # to it. Uncomment only if the default does not work.
          # - --apiserver-host=http://my-address:port
...
  • kubernetes-dashboardポッドのデプロイ
# kubectl create -f kubernetes-dashboard.yaml

次にDNSサービスのデプロイですねー

kube-dns設定

  • kubeletにkube-dnsサービスのIPと管理ドメインを設定する(kube-*)
# sed -i 's/KUBELET_ARGS="/KUBELET_ARGS="--cluster-dns=172.16.0.2 --cluster-domain=cluster.local /g' /etc/kubernetes/kubelet
# systemctl restart kubelet
  • manifestをダウンロード
# curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kube-dns.yaml.sed
# mv kube-dns.yaml.sed kube-dns.yaml
  • manifestを修正して、以下を設定
# sed -i "s/\$DNS_SERVER_IP/172.16.0.2/g" kube-dns.yaml
# sed -i "s/\$DNS_DOMAIN/cluster.local/g" kube-dns.yaml
  • kube-dnsポッドのデプロイ
# kubectl create -f kube-dns.yaml

これでDashboardDNSサービス用のPodなりServiceなりが正常に上がるはずです。

Dashboardはこんな感じ。

f:id:bbrfkr:20180325202055p:plain

DNSサービスの稼働確認をするために、httpdポッドを二つ、別のノードに上げてみましょう。Masterノードで以下を叩きます。

# kubectl create deploy --image httpd httpd-1
# kubectl create deploy --image httpd httpd-2
# kubectl expose deploy httpd-1 --port 80
# kubectl expose deploy httpd-2 --port 80

ポッドが上がったら、以下のコマンドでcurlをそれぞれのポッドにインストールします。

# kubectl exec <httpd-1のポッド> -- apt-get update 
# kubectl exec <httpd-1のポッド> -- apt-get install -y curl
# kubectl exec <httpd-2のポッド> -- apt-get update
# kubectl exec <httpd-2のポッド> -- apt-get install -y curl

curlをインストールしたら、互いのサービスに対して名前解決ができるかを確かめます。

# kubectl exec <httpd-1のポッド> -- curl httpd-2
# kubectl exec <httpd-2のポッド> -- curl httpd-1