Ubuntu 上に Kubernetes + Istio + HAProxy の HA環境をコピペで作る

# はじめに

この記事で目指す構成は、ほぼタイトルの通りですが、

**「kubeadmで作ったHA構成のk8sクラスタにIstioを入れて、HAProxyでプロキシする」**

です。

``kubeadm`` 公式で、[将来的にもっと簡単にHA構成出来るようにする](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/high-availability/)と言っているので、すぐに陳腐化するかもしれませんが…

## 想定するサーバー構成

- プロキシ
- マスターノード × n
- ワーカーノード × n

の構成を目指します。
僕は、ノードは2つずつの環境で作成しました。

## 使い方

タイトル通り、基本的には順番にコピペしていくだけです。
項目の頭にどのサーバー用のコマンド化が書いてあり、関連付けは以下の通りです。

| 頭文字 | サーバー      |
|---    |---            |
| P     | プロキシ       |
| M     | マスターノード |
| W     | ワーカーノード |


記事中にスクリプトがいくつか出てきますが、変更可能個所がある場合は大体冒頭で変数を定義しています。
要件に合わせて、適宜変更してください。

**記事中にいくつかコピペできないところが出てきますので、ご注意ください。**

# コピペコマンド

それでは、粛々とコピペしていきましょう。

## [P/M/W] サーバー初期セットアップ

各種アップデートとタイムゾーン設定

```sh
sudo apt update && sudo apt upgrade -y
sudo timedatectl set-timezone Asia/Tokyo

```

## [P] HAProxy のセットアップ

### インストール

```sh
sudo apt install haproxy -y

```

### cfgファイル作成

| 変数   | 値                                         |
| ---   | ---                                        |
| HOSTS | マスターノードのアドレス(スペース区切り、配列) |

```sh
HOSTS="192.168.1.110 192.168.1.111"
sudo mv /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg~

writeHost() {
sudo sh -c "cat << EOF >> /etc/haproxy/haproxy.cfg

# ==================
# $1
# ==================
frontend $1
    bind *:$2
    mode $3
    default_backend $1

backend $1
    balance     roundrobin
    mode        $3
    option      tcp-check
EOF"
COUNT=1
for host in ${HOSTS}; do
sudo sh -c "cat << EOF >> /etc/haproxy/haproxy.cfg
    server $1-${COUNT} ${host}:$2 check
EOF"
((COUNT++))
done
}

sudo sh -c "cat << EOF > /etc/haproxy/haproxy.cfg
# ==================
# global
# ==================
global
	log /dev/log	local0
	log /dev/log	local1 notice
	chroot /var/lib/haproxy
defaults
	log	global
	mode	http
	option	httplog clf
	option	dontlognull
EOF"

writeHost k8s-api 6443 tcp
writeHost k8s-http 80 http
# writeHost k8s-https 443
sudo service haproxy restart

```

`writeHost k8s-http 80 http` の個所を適宜増やしていけば、他のポートにも対応可能です。

### ※HAProxyの前にさらにnginxがある場合

nginxの設定ファイルで`proxy_http_version 1.1;`を設定しないと、エラー426が起こります。
istioがhttp1.0に対応していない事が原因なようです。

## [M/W] Docker のセットアップ

### インストール

```sh
sudo apt-get update
sudo apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    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 docker-ce-cli containerd.io

```

### 現在のユーザーで Docker を実行可能に

```sh
sudo groupadd docker
sudo usermod -aG docker $(id -u -n)

```

### (Docker バージョン指定が必要な場合)

| 変数           | 値                              |
| ---            | ---                             |
| VERSION_STRING | インストールするDockerのバージョン |

```sh
sudo apt-cache showpkg docker-ce
VERSION_STRING=5:18.09.1~3-0~ubuntu-xenial
sudo apt-get install -y docker-ce=$VERSION_STRING docker-ce-cli=$VERSION_STRING containerd.io

```

## [M/W] kubeadm のインストール

```sh
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

```

### (kubeadm バージョン指定が必要な場合)

| 変数           | 値                              |
| ---            | ---                             |
| VERSION_STRING | インストールするDockerのバージョン |

```sh
sudo apt-cache showpkg kubeadm
VERSION_STRING=1.18.9-00
sudo apt install -y kubeadm=$VERSION_STRING kubelet=$VERSION_STRING kubectl=$VERSION_STRING

```

## [M/W] swapを永続的にオフ

```sh
sudo swapoff -a
sudo sed -i 's:/swap.img:#/spap.img:g' /etc/fstab

```


## [M] 最初のマスターノードセットアップ

### kubeadm init

| 変数               | 値                          |
| ---                | ---                         |
| LOAD_BALANCER_DNS  | HAProxyのアドレス            |
| LOAD_BALANCER_PORT | HAProxyが待ち受けているポート |
| POD_SUBNET         | calicoで使用するサブネット    |

```sh
LOAD_BALANCER_DNS=192.168.1.120
LOAD_BALANCER_PORT=6443
POD_SUBNET=192.168.128.0/17
cat <<EOF > config.yaml
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: stable
apiServer:
  certSANs:
  - "${LOAD_BALANCER_DNS}"
controlPlaneEndpoint: "${LOAD_BALANCER_DNS}:${LOAD_BALANCER_PORT}"
networking:
  podSubnet: "${POD_SUBNET}"
EOF
kubeadm config migrate --old-config config.yaml --new-config kubeadm-config.yaml
rm ~config.yaml
sudo kubeadm init --config=kubeadm-config.yaml

```

**出力結果のコピーを忘れずに！ ↓表示例(コピペコマンドではありません)**

```sh
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of control-plane nodes by copying certificate authorities
and service account keys on each node and then running the following as root:

  kubeadm join 192.168.1.120:6443 --token 77pipk.mh0vazty24yrhedt \
    --discovery-token-ca-cert-hash sha256:99d7dd0f6774d12cb95bca85bf03a666f11d45a1d4a729808974073b405ea126 \
    --control-plane

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.1.120:6443 --token 77pipk.mh0vazty24yrhedt \
    --discovery-token-ca-cert-hash sha256:99d7dd0f6774d12cb95bca85bf03a666f11d45a1d4a729808974073b405ea126
```

### 設定のコピー

```sh
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

```

### calicoを入れる

| 変数         | 値                                              |
| ---          | ---                                            |
| CIDR         | calicoで使用するサブネット(先のPOD_SUBNETと同様)  |

```sh
CIDR=192.168.128.0/17
curl -L -O https://docs.projectcalico.org/v3.8/manifests/calico.yaml
sed -i "s:192.168.0.0/16:${CIDR}:g" ./calico.yaml
kubectl apply -f ./calico.yaml
```

### 他のマスターに証明書を送信

**sshが証明書認証になっていないと、 ``scp`` コマンドの度にパスワードを聞かれます。**

```sh
USER=$(id -u -n)
CONTROL_PLANE_IPS="192.168.1.110 192.168.1.111"
for host in ${CONTROL_PLANE_IPS}; do
    sudo scp /etc/kubernetes/pki/ca.crt "${USER}"@$host:
    sudo scp /etc/kubernetes/pki/ca.key "${USER}"@$host:
    sudo scp /etc/kubernetes/pki/sa.key "${USER}"@$host:
    sudo scp /etc/kubernetes/pki/sa.pub "${USER}"@$host:
    sudo scp /etc/kubernetes/pki/front-proxy-ca.crt "${USER}"@$host:
    sudo scp /etc/kubernetes/pki/front-proxy-ca.key "${USER}"@$host:
    sudo scp /etc/kubernetes/pki/etcd/ca.crt "${USER}"@$host:etcd-ca.crt
    sudo scp /etc/kubernetes/pki/etcd/ca.key "${USER}"@$host:etcd-ca.key
    sudo scp /etc/kubernetes/admin.conf "${USER}"@$host:
done
```

## [M] その他のマスターノードセットアップ

### 認証情報を適切な場所に配置

```sh
USER=$(id -u -n)
sudo mkdir -p /etc/kubernetes/pki/etcd
sudo mv /home/${USER}/ca.crt /etc/kubernetes/pki/
sudo mv /home/${USER}/ca.key /etc/kubernetes/pki/
sudo mv /home/${USER}/sa.pub /etc/kubernetes/pki/
sudo mv /home/${USER}/sa.key /etc/kubernetes/pki/
sudo mv /home/${USER}/front-proxy-ca.crt /etc/kubernetes/pki/
sudo mv /home/${USER}/front-proxy-ca.key /etc/kubernetes/pki/
sudo mv /home/${USER}/etcd-ca.crt /etc/kubernetes/pki/etcd/ca.crt
sudo mv /home/${USER}/etcd-ca.key /etc/kubernetes/pki/etcd/ca.key
sudo mv /home/${USER}/admin.conf /etc/kubernetes/admin.conf
```

### kubeadm join

マスター1の``kubeadm init``で得たコマンドのうち、``--control-plane``が**ついている方**を``sudo``付きで実行します。

**↓例(コピペコマンドではありません)**

```
kubeadm join 192.168.1.120:6443 --token 77pipk.mh0vazty24yrhedt \
    --discovery-token-ca-cert-hash sha256:99d7dd0f6774d12cb95bca85bf03a666f11d45a1d4a729808974073b405ea126 \
    --control-plane
```

### 設定のコピー

```sh
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

```

## [W] ワーカーノードセットアップ

マスター1の``kubeadm init``で得たコマンドのうち、``--control-plane``が**ついていない方**を``sudo``付きで実行します。

**↓例(コピペコマンドではありません)**

```sh
sudo kubeadm join 192.168.1.120:6443 --token 77pipk.mh0vazty24yrhedt \
    --discovery-token-ca-cert-hash sha256:99d7dd0f6774d12cb95bca85bf03a666f11d45a1d4a729808974073b405ea126
```

## トークンとシークレットが無効になった場合

こちらの記事を参照してください。

https://qiita.com/soumi/items/7736ac3aabbbe4fb474a#w-master%E3%83%8E%E3%83%BC%E3%83%89%E3%81%ABjoin%E3%81%99%E3%82%8B

## [M] istioのインストール

### デフォルト状態でインストール

```sh
curl -L https://git.io/getLatestIstio | sh -
for f in ./istio*; do
  cd $f
  break
done
sudo mv bin/istioctl /usr/local/bin/
istioctl manifest apply -y

```

#### 削除する場合

```sh
istioctl manifest generate --set profile=demo | kubectl delete -f -

```

### デフォルト + ダッシュボードをインストール

```sh
curl -L https://git.io/getLatestIstio | sh -
for f in ./istio*; do
  cd $f
  break
done
sudo mv bin/istioctl /usr/local/bin/
istioctl manifest apply -y\
  --set addonComponents.grafana.enabled=true\
  --set addonComponents.kiali.enabled=true\
  --set addonComponents.prometheus.enabled=true

```

#### 削除する場合

```sh
istioctl manifest generate \
  --set addonComponents.grafana.enabled=true\
  --set addonComponents.kiali.enabled=true\
  --set addonComponents.prometheus.enabled=true
  | kubectl delete -f -

```

### NodePortに変更

istio 1.5.1での設定です。
`istio-ingressgateway` の `type` を `NodePort` に変更して、`extenralIPs` に マスターノードのIPを列挙しています。

| 変数   | 値                                     |
| ---    | ---                                    |
| IPS    | マスターノードのIP(スペース区切り、配列)  |

```shell
IPS="192.168.1.110 192.168.1.111"
cat <<EOF > network.yaml
apiVersion: v1
kind: Service
metadata:
  annotations: null
  labels:
    app: istio-ingressgateway
    istio: ingressgateway
    release: istio
  name: istio-ingressgateway
  namespace: istio-system
spec:
  ports:
  - name: status-port
    port: 15020
    targetPort: 15020
  - name: http2
    port: 80
    targetPort: 80
  - name: https
    port: 443
  - name: kiali
    port: 15029
    targetPort: 15029
  - name: prometheus
    port: 15030
    targetPort: 15030                                                                                                                                                                      - name: grafana                                                                                                                                                                            port: 15031                                                                                                                                                                              targetPort: 15031
  - name: tracing
    port: 15032
    targetPort: 15032
  - name: tls
    port: 15443
    targetPort: 15443
  - name: tcp
    port: 31400
  selector:
    app: istio-ingressgateway
    istio: ingressgateway
  type: NodePort
  externalIPs:
EOF
for ip in ${IPS}; do
sh -c "cat << EOF >> network.yaml
    - ${ip}
EOF"
done
kubectl apply -f network.yaml

```

### envoyの自動インジェクト設定

| 変数      | 値                               |
| ---       | ---                              |
| NAMESPACE | 自動インジェクション対象の名前空間  |

```
NAMESPACE=ns-v1
kubectl label namespace ${NAMESPACE} istio-injection=enabled

```

## (option) [M/W] プライベートDockerレジストリに対応

| 変数      | 値                                  |
| ---       | ---                                |
| REGISTRY | プライベートDockerレジストリのアドレス |

注意: 名前空間が毎に保存されるので、対応したい名前空間が複数ある場合は `namspace` の指定が必要です。

```sh
REGISTRY=registory.example.com
docker login $REGISTRY
DOCKERCONFIGJSON=$(cat ~/.docker/config.json | base64 -w 0)
cat <<EOF > private-registry-cred.yaml
apiVersion: v1
kind: Secret
metadata:
  name: regcred
data:
  .dockerconfigjson: ${DOCKERCONFIGJSON}
type: kubernetes.io/dockerconfigjson
EOF
kubectl apply -f private-registry-cred.yaml

```

この状態で、manifestにimagePullSecretsを追加すると、設定済みのプライベートDockerレジストリからpull可能になります。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: private-reg
spec:
  containers:
  - name: private-reg-container
    image: <your-private-image>
  imagePullSecrets:
  - name: regcred
```

# 参考


https://docs.docker.com/install/linux/docker-ce/ubuntu/
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
http://grugrut.hatenablog.jp/entry/2019/06/23/143533
https://qiita.com/megasys1968/items/b84f79b9c3f77de3d4a3
https://bbrfkr.hatenablog.jp/entry/2018/03/15/094536
https://esakat.github.io/esakat-blog/posts/kubernetes-dashboard-v2.0.0/
https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/

istio
https://qiita.com/Ladicle/items/e949b0f68ac18b7a95d8
https://istio.io/docs/setup/getting-started/#uninstall
https://istio.io/docs/setup/install/istioctl/#generate-a-manifest-before-installation