ビビリフクロウの足跡

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

OpenStackとoVirtで自宅クラウド環境を構築してみた

f:id:bbrfkr:20181104113638p:plain

お久しぶりです。4か月間ぐらい全くポストしていませんでしたが生きています。

何やっていたかというと、自宅環境をOpenStackによってプライベートクラウド化しておりました。これが完成しないと諸々の検証ができないので、ブログのポストより優先していたら4か月も経っていた感じです。

4か月間にためていたナレッジは少しずつ文章にしていくつもりですが、まずはどんなプライベートクラウド環境を構築していたのか紹介する記事を書こうと思います。自宅環境を構築したい人、している人の少しでも参考になれば幸いです。

目的

プライベートクラウド環境の目的ですが、以下の通りです。

  • 製品の検証(主にインフラの検証。ハイパバイザレベルでの検証も含む)
  • サービス提供(Wikiサイトなど)

従来はoVirtというOSSの仮想化基盤製品でこれらの目的をこなしていたわけですが、プライベートクラウド化することによって次のことを狙っていました。

  • 様々なサービスのAPIコールによるデプロイ

oVirtはあくまで仮想化基盤なので、あくまでもAmazon EC2やEBS相当の機能しか提供してくれません。昨今のクラウドネイティブなアーキテクチャの学習には不向きだったのです。またコンテナ中核技術であるKubernetesの利便性を最大限引き出すにはクラウドとの連携は必須となります。このためOpenStackによって自宅IaaSを構築しようという考えに至りました。

ただし、OpenStackでは柔軟な仮想マシン設定を行うことが難しくなり、KVMなどのハイパバイザレベルでの動作検証が難しくなります。したがって検証環境すべてをOpenStackに移行したわけではなく、一部リソースをoVirt用に残すようにしました。

アーキテクチャ(物理)

物理構成は以下の通りになります。OpenStack Controllerはロードバランサなどの補助サーバはoVirt上のVMとして構築します。

f:id:bbrfkr:20181104114922p:plain

  • Intel NUC Pentiumモデル x 1 (oVirt Engine用)
  • Intel NUC Corei3モデル x 3 (oVirt Node用)
    • Mem: 32GB/1マシン
    • SSD: 500GB M.2
    • HDD: 1TB
    • NIC: 1GbE x 4
  • Intel NUC Corei3モデル x 3 (OpenStack Compute用)
    • Mem: 32GB/1マシン
    • SSD: 500GB M.2
    • HDD: 1TB
    • NIC: 1GbE x 4

アーキテクチャ(仮想)

VMを含めた構成は以下の通りです。冗長化による性能向上を狙って、OpenStack環境はActive/Activeクラスタを構成しています。(Cinder Volumeを除く)

f:id:bbrfkr:20181104121209p:plain

構築したサービス

プライベートクラウドでは以下のIaaSサービスを提供しています。

  • oVirt
    通常の仮想化環境を提供します。KVM等のハイパバイザ自体を検証したいときはこっちを使います。VMやボリュームのプロビジョニングを行うことができ、通常の仮想化環境といってもAPIがついているので、Ansible等を用いてプロビジョニングを自動化することが可能です。

  • OpenStack Keystone
    AWSにおけるIAM相当の認証基盤を提供します。

  • OpenStack Swift
    AWSにおけるS3相当のオブジェクトストレージ機能を提供します。

  • OpenStack Glance
    AWSにおけるAMI相当のVMイメージストア機能を提供します。Swiftと連携してイメージをSwiftに保存するようにしています。

  • OpenStack Nova
    AWSにおけるEC2相当のVM機能を提供します。

  • OpenStack Neutron
    AWSにおけるVPC、ELB相当の仮想ネットワーク、ソフトウェアロードバランサー機能を提供します。

  • OpenStack Horizon
    AWSにおけるマネジメントコンソール相当のダッシュボード機能を提供します。

  • OpenStack Cinder
    AWSにおけるEBS相当のブロックストレージ機能を提供します。

  • OpenStack Heat
    AWSにおけるCloudFormation相当のオーケストレーション機能を提供します。

  • OpenStack Trove
    AWSにおけるRDS相当のRDBMS機能を提供します。

  • OpenStack Manila
    AWSにおけるEFS相当の共有ファイルシステム機能を提供します。

  • OpenStack Designate
    AWSにおけるRoute53相当のDNS機能を提供します。

  • OpenStack Magnum
    AWSにおけるECS、EKS相当のコンテナオーケストレーション機能を提供します。Kubernetesのデプロイを一発で行えます。

まとめ

OSSを使って自宅環境をプライベートクラウド化してみましたが、ソフトウェアでインフラを制御するのはとても便利で快感です! お時間ある方は是非チャレンジしてみてはいかがでしょうか? 設計や構築方法について疑問がある方は質問していただければ可能な限りお答えしたいと思います!

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を使ってオートスケーリングを実現することができます。