kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

kubeadm on Vagrantでマルチマスターやってみた①

※2020/03/08追記 k8sのセットアップを行うプロビジョニング用スクリプト(scripts/common/00-setup-k8s.sh)については少し変更しています。

前回のhard wayでなんとなくk8sの雰囲気がわかったので、今度はkubeadmを使ってマルチマスターをやってみたいと思います。

ざっとググってみたけど、kubeadmはシングルマスターの記事が多いのと、さらにvagrantになると少しvirtualbox特有のクセみたいなものもあって、なかなかマルチマスターk8s on vagrantな記事はお目にかかりません。であればやってみようというのが今回の目的です。

以下要件。

  • ネットワーク構成は基本的にhard wayを踏襲します。
  • vagrant上で構築します。お手軽だしオンプレに近い環境つくれると思うので。
  • マルチマスター構成、ただしetcdはstacked(さすがにVM台数的に辛い)
    • master x 3
    • worker x 3
    • loadbalancer x 1 ※ master用
  • 構築はkubeadmを使います。
  • CNIはCalico、といってもflannelとあまりよく違いがわかってないけど、Calicoの仕様によりpodネットワークは192.168.0.0/16になります。

こんな感じ。

f:id:kun432:20200224235455p:plain

Vagrant

レポジトリは以下にあります。最初に少しVagrant部分の説明をします。

github.com

Vagrantfile

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "512"
  end

  config.vm.synced_folder "./share", "/Vagrant" , type: "virtualbox"

  # must be at the top
  config.vm.define "lb-0" do |c|
      c.vm.hostname = "lb-0"
      c.vm.network "private_network", ip: "10.240.0.40"

      c.vm.provision :shell, :path => "scripts/common/00-setup-initial.sh"
      c.vm.provision :shell, :path => "scripts/lb/setup-haproxy.sh"

      c.vm.provider "virtualbox" do |vb|
        vb.memory = "256"
      end
  end

  (0..2).each do |n|
    config.vm.define "controller-#{n}" do |c|
        c.vm.hostname = "controller-#{n}"
        c.vm.network "private_network", ip: "10.240.0.1#{n}"
        c.vm.provider "virtualbox" do |v|
          v.gui = false
          v.cpus = 2
          v.memory = 2048
        end

        c.vm.provision :shell, :path => "scripts/common/00-setup-initial.sh"
        c.vm.provision :shell, :path => "scripts/common/00-setup-k8s.sh"
    end
  end

  (0..2).each do |n|
    config.vm.define "worker-#{n}" do |c|
        c.vm.hostname = "worker-#{n}"
        c.vm.network "private_network", ip: "10.240.0.2#{n}"
        c.vm.provider "virtualbox" do |v|
          v.gui = false
          v.cpus = 1
          v.memory = 1024
        end

        c.vm.provision :shell, :path => "scripts/common/00-setup-initial.sh"
        c.vm.provision :shell, :path => "scripts/common/00-setup-k8s.sh"
    end
  end

end

これでloadbalancer x 1、master x 3、worker x 3を構築します。結構リソースは必要です。プロビジョニングでスクリプトをそれぞれ呼んでいます。ちなみにsynced_folderを使っているのは、master間でファイルの受け渡しが発生するためです(sshの設定するのがめんどくさかった)

プロビジョニングで使っているスクリプトの中身はこんな感じです。

scripts/common/00-setup-initial.sh

全台共通で使っています。hostsファイルの設定、SELinuxの停止、firewalldの停止だけです。

#!/bin/bash

set -euo pipefail

cat <<EOF | sudo tee -a /etc/hosts
10.240.0.10 controller-0
10.240.0.11 controller-1
10.240.0.12 controller-2
10.240.0.20 worker-0
10.240.0.21 worker-1
10.240.0.22 worker-2
EOF

# disable SELinux
sudo setenforce 0
sudo sed -i -e "s/^SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config

# disable firewalld
sudo systemctl stop firewalld
sudo systemctl disable firewalld

scripts/lb/setup-haproxy.sh

loadbalancer用のスクリプトです。haproxyの設定を行っています。これはhard wayのものをそのままにいくつかコメントアウトしただけです。(hard wayはubuntuベース、こちらはcentos

#!/bin/bash

set -euo pipefail

yum update -y
yum install -y haproxy

grep -q -F 'net.ipv4.ip_nonlocal_bind=1' /etc/sysctl.conf || echo 'net.ipv4.ip_nonlocal_bind=1' >> /etc/sysctl.conf

cat >/etc/haproxy/haproxy.cfg <<EOF
global
    log /dev/log    local0
    log /dev/log    local1 notice
    chroot /var/lib/haproxy
    #stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    # Default SSL material locations
    ca-base /etc/ssl/certs
    crt-base /etc/ssl/private
    # Default ciphers to use on SSL-enabled listening sockets.
    # For more information, see ciphers(1SSL). This list is from:
    #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
    ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
    ssl-default-bind-options no-sslv3
defaults
    log global
    mode    tcp
    option  tcplog
    option  dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
    #errorfile 400 /etc/haproxy/errors/400.http
    #errorfile 403 /etc/haproxy/errors/403.http
    #errorfile 408 /etc/haproxy/errors/408.http
    #errorfile 500 /etc/haproxy/errors/500.http
    #errorfile 502 /etc/haproxy/errors/502.http
    #errorfile 503 /etc/haproxy/errors/503.http
    #errorfile 504 /etc/haproxy/errors/504.http
frontend k8s
    bind 10.240.0.40:6443
    default_backend k8s_backend
backend k8s_backend
    balance roundrobin
    mode tcp
    server controller-0 10.240.0.10:6443 check inter 1000
    server controller-1 10.240.0.11:6443 check inter 1000
    server controller-2 10.240.0.12:6443 check inter 1000
EOF

systemctl restart haproxy
systemctl enable haproxy

scripts/common/00-setup-k8s.sh

masterとworker用にk8sコンポーネントのインストールと基本設定を行っています。kubernetesやdockerのバージョンは決め打ちにしています。バージョンコントロールは自分で制御したいので。

#!/bin/bash

set -euo pipefail

KUBERNETES_VERSION=1.17.3
DOCKER_VERSION=19.03.4
CONTAINERD_VERSION=1.2.10

# disable swap off
sudo swapoff -a
sudo sed -i '/ swap /s/^\(.*\)$/#\1/g' /etc/fstab
sudo rm -rf /swapfile

# install kubernetes repository
cat <<EOF | sudo tee -a /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
exclude=kube*
EOF

# install kubeadm, kubelet, kubectl
sudo yum install -y kubelet-${KUBERNETES_VERSION} kubeadm-${KUBERNETES_VERSION} kubectl-${KUBERNETES_VERSION} --disableexcludes=kubernetes

# enable kubelet
sudo systemctl enable --now kubelet

# enable network bridge
cat <<'EOF' | sudo tee -a /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system

# install docker
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y --setopt=obsoletes=0 docker-ce-$DOCKER_VERSION docker-ce-cli-$DOCKER_VERSION containerd.io-${CONTAINERD_VERSION}
sudo systemctl enable docker && sudo systemctl start docker
sudo usermod -aG docker vagrant

# cgoup
sudo mkdir -p /etc/docker
cat <<'EOF' | sudo tee -a /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ]
}
EOF
sudo mkdir -p /etc/systemd/system/docker.service.d

# Restart Docker
sudo systemctl daemon-reload
sudo systemctl restart docker

# get private network IP addr and set bind it to kubelet
IPADDR=$(ip a show eth1 | grep inet | grep -v inet6 | awk '{print $2}' | cut -f1 -d/)
sudo sed -i "/KUBELET_EXTRA_ARGS=/c\KUBELET_EXTRA_ARGS=--node-ip=$IPADDR" /etc/sysconfig/kubelet

# restart kubelet
sudo systemctl daemon-reload
sudo systemctl restart kubelet

やっていることはざっくりこんな感じ。

  • kubernetesを動かすにはswapを無効にする必要があります。単純にfstabコメントアウトするだけではダメで、swapファイル自体も消してます。
  • kubernetesyumレポジトリを追加
  • kubeadm, kubelet, kubectlをインストール
  • ネットワークブリッジを有効化するためのカーネルパラメータを変更
  • dockerをインストール
  • dockerでgroupドライバーとしてsystemdを使う設定を追加
  • dockerを再起動
  • vagrant、というかvirtualboxでは標準でNAT用インタフェースとして10.0.2.15が割り当てられますが、これはホスト間の通信に使えません。kubeletは標準でこれをバインドしようとするので、これを回避するために設定を追加
  • kubeletを再起動

Vagrantの起動

master/worker専用のプロビジョニング用ファイルとかもあるんですが、まだ未完成なのでとりあえず進めます。

レポジトリをcloneします。

$ git clone https://github.com/kun432/kubernetes-by-kubeadm-on-vagrant.git
$ cd kubernetes-by-kubeadm-on-vagrant

vagrant upでVMを起動します。結構時間がかかります。マシンパワーがないと辛いかもしれません。

$ vagrant up

7台起動していればOKです。

$ vagrant status
Current machine states:

lb-0                      running (virtualbox)
controller-0              running (virtualbox)
controller-1              running (virtualbox)
controller-2              running (virtualbox)
worker-0                  running (virtualbox)
worker-1                  running (virtualbox)
worker-2                  running (virtualbox)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.

今日はここまで。プロビジョニングしかできていない・・・次回はmaster/workerの設定について。

なお、プロビジョニング用スクリプトは以下を参考にさせてもらいました。ありがとうございます。

qiita.com

Kubernetes The Hard Way をVagrantでやってみた②

前回からの続きです。

kun432.hatenablog.com

Bootstrapping the etcd Cluster

etcdはkubernetesクラスタの状態などのデータを管理します。これを3台のmasterでHAを組む形で設定していきます。

まず、各controllerにログインします。tmuxとか使って並列でやると捗るかもしれません。

$ vagrant ssh controller-0

etcdをインストールします。スクリプトは、vagrantでフォルダ同期が予め設定されているので、ローカルのリポジトリは/vagrantに見えているはずです。

$ /vagrant/scripts/k8s-the-hard-way/0700-download-and-install-etcd.sh

結構時間かかりましたが、これで/usr/local/bin/etcdと/usr/local/bin/etcdctlが配置されます。

次にetcdの設定です。

  • /etc/etcdディレクトリを作成し、ここに認証局の証明書とkube-api-server用のサーバ証明書秘密鍵を配置します
  • /var/lib/etcdディレクトリを作成します。ここに各種情報が保存されます。
  • etcd間の通信に使用する内部IPは各ノードの10.240.0.X/24のIPアドレスになります。ここはGCPとは違うかも。
  • etcdが動作するホスト名を取得します。このホスト名はクラスタ内でユニークでなければなりません。
  • これらを踏まえたsystemd用のunitファイルを生成し配置します。
$ /vagrant/scripts/k8s-the-hard-way/0701-configure-etcd.sh

これで/etc/systemd/system/etcd.serviceが作成されます。

ではetcdの起動設定を行い起動します。

$ /vagrant/scripts/k8s-the-hard-way/0702-start-etcd.sh

etcdが正しく動作しているか確認します。スクリプト内ではetcdctl member listを使ってメンバー一覧を取得しています。。

$ /vagrant/scripts/k8s-the-hard-way/0710-verify-etcd.sh

以下のようにすべてのetcdメンバーが表示されればOKです。

xxxxxxxxxxxxxxxxxx, started, controller-2, https://10.240.0.12:2380, https://10.240.0.12:2379
xxxxxxxxxxxxxxxxxx, started, controller-0, https://10.240.0.10:2380, https://10.240.0.10:2379
xxxxxxxxxxxxxxxxxx, started, controller-1, https://10.240.0.11:2380, https://10.240.0.11:2379

残りのcontrollerノードについても同様に行います。

Bootstrapping the Kubernetes Control Plane

続けて、kubernetesのcontrollerノードとしての設定を行っていきます。

まず、各controllerにログインします。

$ vagrant ssh controller-0

controllerノードに必要なコンポーネントをインストールします。

  • /etc/kubernetes/configディレクトリを作成します。設定ファイルはここに保存されます。
  • kube-apiserver、kube-controller-manager、kube-scheduler、kubectlを/usr/local/bin配下にインストールします。
$ /vagrant/scripts/k8s-the-hard-way/0800-download-and-install-k8s-controllers.sh

kube-apiserverの設定を行います。

  • /var/lib/kubernetesを作成し、認証局・kube-apiserver・サービスアカウント用の証明書と鍵、あと暗号化設定を配置します。
  • 各contollerノードが他のメンバーにアドバタイズするkube-apiserver内部IPは各ノードの10.240.0.X/24のIPアドレスになります。
  • これらを踏まえたsystemd用のunitファイルを生成し配置します。
$ /vagrant/scripts/k8s-the-hard-way/0801-configure-k8s-api-server.sh

kube-controller-managerの設定を行います。

  • kube-controller-manager.kubeconfig を/var/lib/kubernetes配下にコピーします
  • systemd用のunitファイルを生成し配置します。
$ /vagrant/scripts/k8s-the-hard-way/0802-configure-k8s-controller-manager.sh

kube-schedulerの設定を行います。

  • kube-scheduler.kubeconfigを/var/lib/kubernetes配下にコピーします
  • systemd用のunitファイルを生成し配置します。
$ /vagrant/scripts/k8s-the-hard-way/0803-configure-k8s-schedueler.sh

ではこれらの起動設定を行い起動します。

$ /vagrant/scripts/k8s-the-hard-way/0804-start-controller-services.sh

確認です。kubectl get componentstatusesで各コンポーネントを確認しています。

$ /vagrant/scripts/k8s-the-hard-way/0805-verify.sh

こんな感じですべてがHealthyであればOKです。

NAME STATUS MESSAGE ERROR scheduler Healthy ok controller-manager Healthy ok etcd-1 Healthy {"health":"true"} etcd-2 Healthy {"health":"true"} etcd-0 Healthy {"health":"true"}

次にRBACの設定です。RBACはRole Based Access Controlの略で、masterのAPIサーバからworkerのkubeletにアクセスして認可を行うためのモジュールらしいです。ログやメトリクスの取得、podに対するコマンド実行の際に使用されます。ちょっと細かいところは後で読むこととします。

qiita.com

以下が作成されます。

  • ClusterRole
    • クラスタに対するロールを作成し、ノードに対するいろいろな権限を付与
  • ClusterRoleBinding
    • 上記のロールをkubernetesユーザと紐付ける
$ /vagrant/scripts/k8s-the-hard-way/0810-rbac-for-kubelet-auth.sh

フロントエンドのロードバランサーの確認です。オリジナルではGCPなのでNetwork Load Balancerを作成してましたが、Vagrant版ではlb-0になります。ローカルのMacから10.240.0.40に対して、curlでアクセスして、バージョンを確認します。

$ ./scripts/k8s-the-hard-way/0820-verify-frontend-lb.sh

以下のように表示されればOKです。

{
  "major": "1",
  "minor": "10",
  "gitVersion": "v1.10.2",
  "gitCommit": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "gitTreeState": "clean",
  "buildDate": "2018-04-27T09:10:24Z",
  "goVersion": "go1.9.3",
  "compiler": "gc",
  "platform": "linux/amd64"

Bootstrapping the Kubernetes Worker Nodes

次はworkerノードの設定です。各workerにsshでログインします。

$ vagrant ssh worker-0

まずworkerノードに必要なパッケージやコンポーネントをインストール、配置します。個々のコンポーネントの意味はよく理解できてませんが、nodeの場合、コンテナのランタイムやネットワーク関連のものが多いですね。

  • socat
  • conntrack
  • ipset
  • crictl
  • runsc
  • cni-plugins
  • containerd
  • kubectl
  • kube-proxy
  • kubelet
$ /vagrant/scripts/k8s-the-hard-way/0900-download-and-install-workers.sh

CNIの設定を行います。CNI はContainer Network Interfaceの略で、Linuxコンテナ作成時・削除時にネットワーク周りをよしなに設定してくれるものらしいです。Linxuのネットワーク周り、ちょっと難しくなるとさっぱりわからない、ネットワークの基本知識が足りない気がする・・・

$ /vagrant/scripts/k8s-the-hard-way/0901-configure-cni.sh

コンテナランタイムであるcontainerdの設定を行います。dockerも内部的にはcontainerdを使ってるらしいです。

  • 設定ファイルを作成
  • systemdで起動するようにunitファイルを用意
$ /vagrant/scripts/k8s-the-hard-way/0902-configure-containerd.sh

kubeletの設定を行います。

  • 証明書を所定のパスに配置
  • 設定ファイルを作成
  • systemdで起動するようにunitファイルを用意
$ /vagrant/scripts/k8s-the-hard-way/0903-configure-kubelet.sh

kube-proxyの設定を行います。

  • kube-proxyの設定ファイルを所定のパスに配置
  • systemdで起動するようにunitファイルを用意
$ /vagrant/scripts/k8s-the-hard-way/0904-configure-kube-proxy.sh

workerのサービス起動を設定し、起動します。

$ /vagrant/scripts/k8s-the-hard-way/0905-start-worker-services.sh

ローカルのmacからサービスが正常に動いているか確認します。といっても中身はcontrollerからkubectl get nodesしてますね。

$ ./scripts/k8s-the-hard-way/0910-verify-worker.sh

以下のように表示されればOKです。

AME       STATUS    ROLES     AGE       VERSION
worker-0   Ready     <none>    1m        v1.10.2
worker-1   Ready     <none>    1m        v1.10.2
worker-2   Ready     <none>    1m        v1.10.2

Configuring kubectl for Remote Access

ローカルのmacで、リモートからadminユーザでkubectlするための設定を行っていきます。リモートからのアクセスはlb-0のIPアドレスに対して行います。

$ ./scripts/k8s-the-hard-way/1000-admin-kubeconfig.sh

確認します。

$ ./scripts/k8s-the-hard-way/1001-verify-admin-kubeconfig.sh

スクリプト内ではkubectl get componentstatusとkubectl get nodesが実行されており、以下のように表示されればOKです。

NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok
etcd-0               Healthy   {"health":"true"}
etcd-2               Healthy   {"health":"true"}
controller-manager   Healthy   ok
etcd-1               Healthy   {"health":"true"}

NAME       STATUS   ROLES    AGE   VERSION
worker-0   Ready    <none>   11m   v1.10.2
worker-1   Ready    <none>   11m   v1.10.2
worker-2   Ready    <none>   11m   v1.10.2

Provisioning Pod Network Routes

ここはGCP向けなのでスキップします。

Deploying the DNS Cluster Add-on

CoreDNSを使ってDNSベースのサービスディスカバリーの設定を行います。kube-dnsのセットアップが行われているようです。

$ ./scripts/k8s-the-hard-way/1200-dns-cluster-add-on.sh
./scripts/k8s-the-hard-way/1200-dns-cluster-add-on.sh: line 5: watch: command not found

ということで、watchコマンドをインストールする必要がありました。brewでインストールします。

$ brew install watch

で、スクリプトを再度実行します。多分同じことを再度やっても大丈夫なはず。以下のように表示されればOKです。

Every 2.0s: kubectl get pods -l k8s-app=kube-dns -n kube-system             xxxxxx: Sun Feb 16 22:01:44 2020

NAME                        READY   STATUS    RESTARTS   AGE
kube-dns-598d7bf7d4-4st68   3/3     Running   0          20m

では確認です。名前解決用にbusyboxのpodを用意します。

$ ./scripts/k8s-the-hard-way/1210-verify-dns.sh

暫く待つとこんな感じになります。

Every 2.0s: kubectl get pods -l run=busybox                                                        xxxxxx: Sun Feb 16 22:06:00 2020

NAME                       READY   STATUS    RESTARTS   AGE
busybox-68654f944b-vs5xm   1/1     Running   0          38s

このpodでnslookupします。

$ ./scripts/k8s-the-hard-way/1211-verify-dns.sh

ちょっと私の手元の環境では解決できているものもあるけど、そうでないものもあるって感じになってますね。。。。

Server:      10.32.0.10
Address:    10.32.0.10:53

Name:   kubernetes.default.svc.cluster.local
Address: 10.32.0.1

*** Can't find kubernetes.svc.cluster.local: No answer
*** Can't find kubernetes.cluster.local: No answer
*** Can't find kubernetes.lan: No answer
*** Can't find kubernetes.default.svc.cluster.local: No answer
*** Can't find kubernetes.svc.cluster.local: No answer
*** Can't find kubernetes.cluster.local: No answer
*** Can't find kubernetes.lan: No answer

で調べてみると、どうもbusyboxのタグ指定が必要らしい。

kubectl delete podで削除したんだけど、また上がってくるので、deployment消してからpod削除しました。タグ指定して実行してみる。

$ kubectl delete deployment busybox
$ kubectl delete pod busybox-XXXXXXXX-XXXXX --grace-period=0 --force
$ kubectl run busybox --image=busybox:1.28 --command -- sleep 3600

再度実行。

$ ./scripts/k8s-the-hard-way/1211-verify-dns.sh

うまくいきました。

Server:    10.32.0.10
Address 1: 10.32.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes
Address 1: 10.32.0.1 kubernetes.default.svc.cluster.local

Smoke Test

ではざっと確認していきます。ここはスクリプトの中身を見て実行していきたいと思います。

データの暗号化。

$ ./scripts/k8s-the-hard-way/1300-data-encryption.sh

以下のようにenc:aescbc:v1:key1が表示されていればOKです。

secret/kubernetes-the-hard-way created

00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
00000010  73 2f 64 65 66 61 75 6c  74 2f 6b 75 62 65 72 6e  |s/default/kubern|
00000020  65 74 65 73 2d 74 68 65  2d 68 61 72 64 2d 77 61  |etes-the-hard-wa|
00000030  79 0a 6b 38 73 3a 65 6e  63 3a 61 65 73 63 62 63  |y.k8s:enc:aescbc|
00000040  3a 76 31 3a 6b 65 79 31  3a a7 bd ef ee cc 8b 98  |:v1:key1:.......|
00000050  ed fc 27 69 a1 48 9e fb  82 29 38 10 e2 fb c6 a2  |..'i.H...)8.....|
00000060  12 18 c8 62 8e 15 22 1e  5a 90 87 66 26 7a 9a 5b  |...b..".Z..f&z.[|
00000070  e6 05 c3 39 53 aa 14 74  01 66 bb 38 f7 8d 78 42  |...9S..t.f.8..xB|
00000080  b1 d7 34 21 63 62 c6 f8  34 33 6b 98 22 56 73 1a  |..4!cb..43k."Vs.|
00000090  e1 16 14 2b 25 84 70 f7  7a 7b e5 91 ee b5 fc 87  |...+%.p.z{......|
000000a0  a9 69 f5 e8 86 85 0c 73  00 4f cc a2 aa e6 d3 cd  |.i.....s.O......|
000000b0  f2 96 29 51 8c 80 2f 9f  e5 eb db 3f a9 a5 1b 70  |..)Q../....?...p|
000000c0  16 1a c5 76 1e 7e c2 8f  1a 9f 99 12 7e 42 84 88  |...v.~......~B..|
000000d0  2f f5 5c dc a4 ff 0a 2b  54 fc 1f 2e 01 5f bb 92  |/.\....+T...._..|
000000e0  9c 0f 8e dc 59 77 8f 12  24 0a                    |....Yw..$.|
000000ea

次はdeployment。nginxでwebサーバを立てます。

$ ./scripts/k8s-the-hard-way/1310-deployments.sh

以下のように表示されればOK。

Every 2.0s: kubectl get pods -l run=nginx 

NAME                     READY   STATUS    RESTARTS   AGE
nginx-65899c769f-8qvdn   1/1     Running   0          1m

ポートフォワーディングでアプリにアクセスできることを確認します。ここはスクリプト内のコマンドを順次実行してみます。

$ POD_NAME=$(kubectl get pods -l run=nginx -o jsonpath="{.items[0].metadata.name}")
$ kubectl port-forward $POD_NAME 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

違うターミナルを開いて、HTTPでアクセスしてみる。レスポンスが帰ってくればOK。

$ curl --head http://127.0.0.1:8080
HTTP/1.1 200 OK
Server: nginx/1.17.8
Date: Sun, 16 Feb 2020 14:00:43 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 21 Jan 2020 13:36:08 GMT
Connection: keep-alive
ETag: "5e26fe48-264"
Accept-Ranges: bytes

もとのターミナルはCtrl+Cで止めておく。

コンテナのログが取得できるか。POD_NAMEは前のを引き続き使う。

$ kubectl logs $POD_NAME

ちゃんと取れてます。

127.0.0.1 - - [16/Feb/2020:13:33:30 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.54.0" "-"
127.0.0.1 - - [16/Feb/2020:14:00:43 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.54.0" "-"
127.0.0.1 - - [16/Feb/2020:14:01:50 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.54.0" "-"

コンテナでコマンドを実行する。バージョンが取得できてますね。

$ kubectl exec -ti $POD_NAME -- nginx -v
nginx version: nginx/1.17.8

serviceを使ってアプリケーションを公開します。nginxのdeploymentをNodePortで公開します。

$ kubectl expose deployment nginx --port 80 --type NodePort
service/nginx exposed

nginxに割り当てられたNodePortを取得します。

$ NODE_PORT=$(kubectl get svc nginx --output=jsonpath='{range .spec.ports[0]}{.nodePort}')
$ echo $NODE_PORT
32601

ノードのIPでアクセスしてみます。

$ curl -I http://10.240.0.20:${NODE_PORT}
HTTP/1.1 200 OK
Server: nginx/1.17.8
Date: Sun, 16 Feb 2020 14:08:31 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 21 Jan 2020 13:36:08 GMT
Connection: keep-alive
ETag: "5e26fe48-264"
Accept-Ranges: bytes

できてますね。ちなみにノードのIPを変更しても同じようにレスポンスが返ってきます。

$ curl -I http://10.240.0.21:${NODE_PORT}
$ curl -I http://10.240.0.22:${NODE_PORT}

はい、これで終了です。

Cleaning Up

削除はvagrant destroyで削除します。

$ vagrant destroy

結構長かったし、まだよくわかってないことがたくさんありますが、いろんなコンポーネントで構成されていて、なんとなくでもイメージができたというのは収穫だったかな。

次はkubeadmを使っ他クラスタ構築をやってみようと思います。kubeadmでも面倒、というような印象がありますが、hard wayをやった後だと多分楽に感じるのではないかと。

参考にさせていただいたサイト

ありがとうございました。

Kubernetes The Hard Way をVagrantでやってみた①

今回はAlexaと全然関係ない話です。いろいろあってk8sをやっています。で、とりあえずやってみたものの、抽象化度合いが深すぎて全く理解できていない感を拭えないので、これをやってみようという話です。

オリジナルはGCPを使うのですが、もうちょっと手触り感ある感じでやりたい、ということで、以下を参考に進めたいと思います。

なおVagrant版のgithubはこちらです。サラッと流すのではなく、中身も読んで行きたいと思います。

多分1回では終わらないのとしっかり理解を深めるのが目的なので、複数回に分けて説明等を追加しながらやっていきたいと思います。


Vagrantの準備

Vagrantが入ってない方はインストールしてください。Macならhomebrew caskでVagrantVirtualbox両方入れてしまうのがかんたんでおすすめです。

$ brew cask install vagrant virtualbox

Vagrantプラグインも追加しましょう。vagrant-vbguestを入れておくと、VirtualboxのGuestAdditionをバージョンに合わせて入れてくれるのでおすすめです。

$ vagrant plugin install vagrant-vbguest

もう一つプラグインを追加。ローカルからvagrantにscpを行うステップがあるので、vagrant-scpプラグインを追加しておきます。

$ vagrant plugin install vagrant-scp

Vagrant版のVagrantfileを見ると、Ubuntu18.04LTSを使うようです。何故か私の環境ではboxの展開にコケてたので、手動でboxは先に追加してます。

$ vagrant box add ubuntu/bionic64

レポジトリをクローン

$ git clone https://github.com/kenfdev/kubernetes-the-hard-way-vagrant.git
$ cd kubernetes-the-hard-way-vagrant

構築する環境

Vagrant仮想マシンを起動します。

$ vagrant up

合計7台の仮想マシンが起動します(Macbook Pro Late2016 15インチのフルスペックでもかなり時間かかります。)。Vagrantfileの中身を追いかけるとこんな感じに見えます。

f:id:kun432:20200215231337p:plain

  • 10.240.0.0/24に LB ✕ 1、master ✕ 3、worker ✕ 3が作成される
  • LBはHAProxyをつかってmasterのapiserverの負荷分散を行う(scripts/bootstrap/vagrant-setup-haproxy.sh)
  • それぞれのworkerのインタフェースに対して、10.200.X.0/24のネットワークへのルーティングを設定する。(scripts/bootstrap/vagrant-setup-routes.sh)。多分これがpodのネットワーク。
  • master、workerはhostsファイルで名前解決(scripts/bootstrap/vagrant-hosts-file.sh)

はい、これで準備ができました。では順次やっていきます。

Prerequisites

特にやることはないのでスキップ

Installing the Client Tools

クライアント側のツールをインストールします。cfsslとcfssljsonはhomebrewを使いました。

$ brew install cfssl
$ cfssl version
Version: 1.4.1
Runtime: go1.13.4
$ cfssljson --version
Version: 1.4.1
Runtime: go1.13.4

cfsslとかcfssljson初めて知ったけど、SSL証明書に必要な秘密鍵CSRjsonで作れるというものらしい。これは便利ですね。

aligach.net

あと、kubectlはもともとdocker for macが入っていてkubectkも入っているのでそれを使ってみようと思います。

$ kubectl version --client
Client Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:16:51Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"darwin/amd64"}

Provisioning Compute Resources

特にやることはないのでスキップ

Provisioning the CA and Generating TLS Certificates

k8sでは各種コンポーネントで証明書が必要になりますので、まず最初に公開鍵基盤として、認証局の証明書と秘密鍵を作成します。スクリプトの中では、設定ファイルとCSRを作成して、それをもとに認証局の証明書と秘密鍵を生成します。

$ scripts/k8s-the-hard-way/0400-certificate-authority.sh

これで、

  • ca-config.json
  • ca-key.pem
  • ca.pem

ができます。

次にクライアント・サーバ証明書を作成します。これらはk8sの各コンポーネントで使用します。

まず、k8sのadminユーザ用のクライアント証明書を作成します。ここで最初に作成した認証局の署名が入った証明書が作成されます。

$ scripts/k8s-the-hard-way/0410-admin-client-certificate.sh

これで、

  • admin-key.pem
  • admin.pem

ができます。

次にkubeletのクライアント証明書。kubeletはworker nodeで動作するエージェントで、各ノード内でpodの起動管理を行います。k8sにはNode Authorizerという権限管理の仕組みがあり、ノード、すなわちkubeletからのAPIリクエストをそのノードに割当てられたpodが必要とするリソースに限定します。ここでクライアント証明書が必要となります。

$ scripts/k8s-the-hard-way/0411-kubelet-client-certificates.sh

これで、各ノード用の証明書と秘密鍵が作成されます。

  • worker-0-key.pem
  • worker-0.pem
  • worker-1-key.pem
  • worker-1.pem
  • worker-2-key.pem
  • worker-2.pem

次にkube-controller-manager用の証明書と秘密鍵を作成します。controller-managerはmaster上で、各種controllerを実行管理します。具体的には、ノードダウン時の通知や対応を行うnode controllerや、レプリケーションを行うreplication controllerなどです。

$ scripts/k8s-the-hard-way/0412-controller-manager-client-certfiicate.sh

kube-controller-manager用の証明書と秘密鍵が作成されます。

  • kube-controller-manager-key.pem
  • kube-controller-manager.pem

次にkube-proxy用のクライアント証明書・秘密鍵を作成します。kube-proxyは各worker上で、serviceとpod間のルーティングを行います。

$ scripts/k8s-the-hard-way/0413-kube-proxy-client-certificate.sh

以下が作成されます。

  • kube-proxy-key.pem
  • kube-proxy.pem

次。kube-scheduler用のクライアント証明書・秘密鍵を作成します。kube-schedulerはmaster上で動作し、podのノードへの割当を行うスケジューラです。

$ scripts/k8s-the-hard-way/0414-scheduler-client-certificate.sh

以下が作成されます。

  • kube-scheduler-key.pem
  • kube-scheduler.pem

次に、kube-api-server用のサーバ証明書秘密鍵を作成します。kube-api-serverがkubernetesをコントロールするためのAPIフロントエンドになります。このときホスト名は以下となっています。

この中で、10.32.0.1というのがどこから来ているかわからなかったのですが、ちゃんとオリジナルには書いてありました。オリジナルのホスト名が増えてたのでそこも少し修正。

$ scripts/k8s-the-hard-way/0415-kubernetes-api-server-certificate.sh

以下ができます。

最後にServiceAccount用のキーペアを作成します。ServiceAccountはアプリケーション内からAPIを叩く際の権限や認可を行う仕組み。GCPだとそのままだし、AWSだとロールみたいなものだと思えばよいのかなと思ってます。

$ scripts/k8s-the-hard-way/0420-service-account-key-pair.sh

以下ができます。

  • service-account-key.pem
  • service-account.pem

これで一通り、証明書と秘密鍵が生成されました。これをmasterとworkerに配置していきます。このときvagrant-scpプラグインが必要となります。

  • 各workerには以下を配置します。
    • ca.pem
    • worker-?-key.pem
    • worker-?.pem
  • masterには以下を配置します。
$ scripts/k8s-the-hard-way/0430-distribute-certificates.sh

kube-proxy、kube-controller-manager、kube-scheduler、kubeletについては次項で設置します。

Generating Kubernetes Configuration Files for Authentication

kubenetesの設定ファイルを作成します。kube-proxy、kube-controller-manager、kube-scheduler、kubeletの各クライアントとadminユーザの設定を行うことにより、kube-api-serverに接続ができるようになります。

概ねスクリプトの中身はこんな感じのことをやってるようです。

  • kube-api-serverのIPアドレスは、worker側設定ファイルではlb-0の10.240.0.40を指定(HAのため)、master側設定ファイルでは127.0.0.1を指定します。
  • kubectl configコマンドを使ってクラスタ、認証情報、コンテキストをセットしたworker-?.kubeconfigを生成します。

ではやっていきましょう。まずworker側。

kubelet。

$ scripts/k8s-the-hard-way/0500-kubelet-kubeconfig.sh

以下ができます。

  • worker-0.kubeconfig
  • worker-1.kubeconfig
  • worker-2.kubeconfig

次にkube-proxy。

$ scripts/k8s-the-hard-way/0501-kube-proxy-kubeconfig.sh
  • kubelet.kubeconfig

master側です。

kube-controller-manager。

$ scripts/k8s-the-hard-way/0502-kube-controller-manager-kubeconfig.sh

kube-scheduler。

$ scripts/k8s-the-hard-way/0503-kube-scheduler-kubeconfig.sh

adminユーザ。

$ scripts/k8s-the-hard-way/0504-admin-kubeconfig.sh

これで以下が生成されます。

  • kube-proxy.kubeconfig
  • kube-controller-manager.kubeconfig
  • kube-scheduler.kubeconfig
  • admin.kubeconfig

最後にこれを配置していきます。

  • worker
    • worker-?.kubeconfig
    • kube-proxy.kubeconfig
  • master
    • kube-controller-manager.kubeconfig
    • kube-scheduler.kubeconfig
    • admin.kubeconfig
$ scripts/k8s-the-hard-way/0510-distribute-kubeconfig.sh

Generating the Data Encryption Config and Key

kubernetesは様々なデータを保持しますが、これらを暗号化することができます(Kubernetes Secrets)。そこで暗号化の設定と鍵の作成を行います。

スクリプト内では以下が行われます。

  • 暗号化の鍵を作成
  • 暗号化設定を生成し、鍵を埋め込む
$ scripts/k8s-the-hard-way/0600-encryption-config.sh

encryption-config.yamlが作成されますので、これをmasterに配布します。masterに配布するのはetcdが暗号化データを管理するためです。

$ scripts/k8s-the-hard-way/0610-distribute-encryption-config.sh

今日はここまで。準備段階だけでも結構かかった気がしますが、引き続き。