Kubernetes 構築手順 Proxy編 ※大体コピペでOK

# Kubernetes 環境構築 Proxy編

Kubernetes(以下、K8s)をイントラネット内で構築する手順です。
K8s環境構築の流れを理解しつつ、作業はなるべくコピペで可能なように記載しています。

## 1. モチベーション

- K8sを会社内の開発イントラネット内環境で構築したい。
(プロキシサーバ経由で開発イントラネット内環境からインターネットにアクセスできる場合)
- K8sを使ってみたいが、構築のハードルが高い。

---

## 2. 手順

本手順で以下のK8sクラスタ構成を構築します。

- 準備するもの
  - OS : Centos7.4（minimal)
  - PC 3台 ※仮想でも可
    - CPU 2Core 以上
    - Memroy 2GB 以上
- 出来上がるもの
  - K8s クラスタ構成 ※Masterはシングル
    - Master
    - Worker
    - Worker

### 2.1. Docker環境の構築(全ノード対象)

K8sは複数ノードにまたがりコンテナの駆動を管理するツールで、実際のコンテナの実行はDockerを経由しています。
また、K8sの機能もコンテナとして動作しているため、クラスタの全ノードでDockerが動作することが必要です。

すでにDockerが使えている場合は先に進んでもらって結構です。
ただし、**※公式推奨**と **※後述の作業に必要** の作業は確認しておいてください。

#### 2.1.1. CentOS7のインストール(CLI)と基本設定

1. CentOSをインストールします。
2. 開発イントラネット内通信が可能となる設定をし、ローカルネットワークに接続可能な状態にしておきます。
   IP,サブネットマスクの固定/MACアドレス登録など
3. swapを無効化します。 **※公式推奨**
4. SELINUX無効化してOS再起動します。
5. yumのプロキシ設定
vi /etc/yum.conf で以下のように編集→保存

```sh
#swapを無効化
swapoff -a
sed -i -e "/swap/d" /etc/fstab
cat /etc/fstab
#SELINUX無効化してOS再起動
sed -i -e "s/^SELINUX=enforcing$/SELINUX=disabled/g" /etc/selinux/config
reboot
```

```sh
#yumのプロキシ設定
cat <<EOF >> /etc/yum.conf
proxy=http://{プロキシIP}:{プロキシポート}
EOF
```

#### 2.1.2. Dockerのインストールと設定

1. Dockerインストールします。 最後にバージョンが表示されれば成功です。
2. Dockerのネットワーク設定[^1] **※後述の作業に必要** とhello-worldコンテナの実行をします。
3. うまく行かない場合は、プロキシ設定を確認します。

[^1]: NO_PROXYにはすべてのk8s master/worker nodeのIPアドレスとk8sの各Serviceに割り振られる仮想IP(10.96.0.0/12)とpodネットワークプラグインが展開するOverlayネットワークのIPアドレス範囲(10.244.0.0/16)をCIDRで記載しておく。

```sh
#Dockerインストール
yum install -y yum-utils device-mapper-persistent-data lvm2　#Docker ceに必要なパッケージ
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo #Docker ceのリポジトリ追加
yum install -y --setopt=obsoletes=0 docker-ce-18.06.1.ce #Docker ceインストール
docker version #Dockerバージョン確認
```

↓Dockerバージョン確認結果

```shell-session
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a
 Built:             Tue Aug 21 17:23:03 2018
 OS/Arch:           linux/amd64
 Experimental:      false
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
[root@K8sWorker1 ~]# docker version #Dockerバージョン確認
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a
 Built:             Tue Aug 21 17:23:03 2018
 OS/Arch:           linux/amd64
 Experimental:      false
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
```

```sh
#Dockerのネットワーク設定
systemctl disable firewalld #Firewall無効化
systemctl stop firewalld #Firewall停止
mkdir -p /etc/systemd/system/docker.service.d # dockerのプロキシ設定
cat <<EOF > /etc/systemd/system/docker.service.d/http-proxy.conf #Proxy設定
[Service]
Environment="HTTP_PROXY=http://{プロキシIP}:{プロキシポート}" "HTTPS_PROXY=http://{プロキシIP}:{プロキシポート}" "NO_PROXY=localhost,127.0.0.1,10.96.0.0/12,10.244.0.0/16,{master nodeのIPアドレス},{worker node1のIPアドレス},{worker node2のIPアドレス}"
EOF
systemctl daemon-reload #プロキシ反映
systemctl enable docker && systemctl start docker #Docker再起動
docker run --rm hello-world #hello-worldコンテナ起動
```

↓ hello-worldコンテナ起動結果

```shell-session

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```


```sh
# プロキシ設定を確認
docker info | grep Proxy
```

---

### 2.2. kubeadmインストール～kubelet有効化 (全ノード対象)

#### 2.2.1. kubeadmインストール

1. kubeadmのためのプロキシを設定してOS再起動します。[^1]
2. k8sのためのyumリポジトリを追加します。
3. kubeadmと関連パッケージのインストールします。 [^2]

[^2]: 1.16.0に固定 k8sとバーション合わせないといけないので注意

```sh
#kubeadmのためのプロキシを設定してOS再起動
cat <<EOF >> ~/.bash_profile
PROXY_PORT={プロキシポート}
PROXY_HOST={プロキシIP}
http_proxy=http://\$PROXY_HOST:\$PROXY_PORT
HTTP_PROXY=\$http_proxy
https_proxy=\$http_proxy
HTTPS_PROXY=\$http_proxy
no_proxy="localhost,127.0.0.1,10.96.0.0/12,10.244.0.0/16,{master nodeのIPアドレス},{worker node1のIPアドレス},{worker node2のIPアドレス}"
EOF
reboot
```

```sh
#k8sのためのyumリポジトリを追加
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
sslverify=0
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
#kubeadmと関連パッケージのインストール
yum install -y kubelet-1.16.0-0.x86_64 kubeadm-1.16.0-0.x86_64 kubectl-1.16.0-0.x86_64 --disableexcludes=kubernetes
```

#### 2.2.2. kubelet有効化

1. リソース管理ドライバをcgroupfsに統一します。
   ※Docker daemonの設定(デフォルトではsystemd)をkubelet側(cgroupfs)に合わせる。
2. kubelet有効化と確認をします。※起動してなくてOK
3. ブリッジの設定をします。

```sh
#リソース管理ドライバをcgroupfsに統一
cat << EOF > /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=cgroupfs"]
}
EOF
systemctl restart docker
docker info | grep -i cgroup
```

```sh
# kubelet有効化と確認
systemctl enable kubelet && systemctl start kubelet
systemctl status kubelet
```

↓ kubelet確認

```shell-session
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)
  Drop-In: /usr/lib/systemd/system/kubelet.service.d
           mq10-kubeadm.conf
   Active: activating (auto-restart) (Result: exit-code) since Tue 2020-06-02 17:00:13 JST; 932ms ago
     Docs: https://kubernetes.io/docs/
  Process: 16490 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS (code=exited, status=255)
 Main PID: 16490 (code=exited, status=255)

Jun 02 17:00:13 K8sWorker2.localdomain systemd[1]: Unit kubelet.service enter...
Jun 02 17:00:13 K8sWorker2.localdomain systemd[1]: kubelet.service failed.
Hint: Some lines were ellipsized, use -l to show in full.
```

```sh
#ブリッジの設定
cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system
```

---

### 2.3. K8sクラスタ構築

#### 2.3.1. Masterの構築 (Masterノード対象)

この作業はMasterノードでのみ行います。

1. /tmpに以下のファイルを配置しておきます。
>   https://github.com/coreos/flannel/blob/master/Documentation/kube-flannel.yml
> kube-flannel-ds-amd64がPullできない場合があります。
> kube-flannel.ymlを編集し、今回はイメージのロード先を easzlabに変更しました。 ※2020/06/02 時点
2. kubeadmを実行します。
   tokenが出力されるので控えておきます。
3. 設定の編集をします。
   最後にcreatedが10個表示されればOK
4. Podを確認します。
   Podが8個あり、すべてRunningになっていればOK

```yaml
# イメージのロード先変更 ※他箇所あり
initContainers:
  - name: install-cni
    image: easzlab/flannel:v0.12.0-amd64
    command:
```

```sh
#kubeadmを実行
kubeadm init --kubernetes-version 1.16.0  --apiserver-advertise-address={master nodeのIPアドレス} --pod-network-cidr=10.244.0.0/16 --token-ttl 0 --image-repository gotok8s
```

↓kubeadmを実行結果

```shell-session
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/

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

kubeadm join {master nodeのip}:6443 --token oe4by1.8wxrpf52w2y6xulw \
    --discovery-token-ca-cert-hash sha256:a184db6aaaa3529ea9cb4f46038945ceae4878e16f21213aac1a605583071d19
```

```sh
# 設定の編集
mkdir -p $HOME/.kube #これをやらないと動作しない場合あり。
sudo cp -f /etc/kubernetes/admin.conf $HOME/.kube/config #これをやらないと動作しない場合あり。
sudo chown $(id -u):$(id -g) $HOME/.kube/config #これをやらないと動作しない場合あり。

kubectl apply -f /tmp/kube-flannel.yml #kube-flannel設定反映
```

```sh
#Podを確認
kubectl get pods --all-namespaces
```

↓Pod確認結果

```shell-session
NAMESPACE     NAME                                             READY   STATUS    RESTARTS   AGE
kube-system   coredns-8655596978-ff4rv                         1/1     Running   0          69s
kube-system   coredns-8655596978-tprdp                         1/1     Running   0          69s
kube-system   etcd-k8smaster1.localdomain                      1/1     Running   0          27s
kube-system   kube-apiserver-k8smaster1.localdomain            1/1     Running   0          19s
kube-system   kube-controller-manager-k8smaster1.localdomain   1/1     Running   0          12s
kube-system   kube-flannel-ds-amd64-7vjkq                      1/1     Running   0          37s
kube-system   kube-proxy-4562b                                 1/1     Running   0          69s
kube-system   kube-scheduler-k8smaster1.localdomain            1/1     Running   0          9s
```


#### 2.3.2. Workerの構築 (Workerノード対象)

この作業はWorkderノードでのみ行います。

1. Masterから設定のコピーをします。
2. クラスターに参加します。

```sh
#Masterから設定のコピー
mkdir -p /etc/cni/net.d/
scp {master nodeのIPアドレス}:/etc/cni/net.d/10-flannel.conflist /etc/cni/net.d/10-flannel.conflist
#クラスターに参加
kubeadm join {master nodeのIPアドレス}:6443 --token {上述の作業で控えた値} \
    --discovery-token-ca-cert-hash sha256:{上述の作業で控えた値}
```

#### 2.3.3. ノードの確認

- それぞれのノードがクラスタに参加していることを確認します。

```sh
#ノードの確認
kubectl get nodes
```

↓ ノードの確認結果

```shell-session
NAME                     STATUS   ROLES    AGE     VERSION
k8smaster1.localdomain   Ready    master   17h     v1.16.0
k8sworker1.localdomain   Ready    <none>   2m26s   v1.16.0
k8sworker2.localdomain   Ready    <none>   82s     v1.16.0
```

---

## 3. 動作確認

### 3.1. ダッシュボードの表示

1. 外部からアクセスできるようにrecommended.yamlを編集し、Masterノードの/tmpに配置しておきます。
https://github.com/kubernetes/dashboard
2. ダッシュボードのPodを作成します。
3. admin-userを作成し、トークンを作ります。
4. 以下URLにアクセスし、トークンを入力するとダッシュボードが開きます。
   https://{master nodeのIP}:30843

```yaml
---
# 外部からアクセスできるようにrecommended.yaml編集
kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  type: NodePort #追加
  ports:
    - port: 443
      targetPort: 8443
      nodePort: 30843 #追加
  selector:
    k8s-app: kubernetes-dashboard
---
```

```sh
#ダッシュボードのPodを作成
kubectl apply -f /tmp/recommended.yaml
kubectl get pod --namespace=kubernetes-dashboard
```

```sh
# admin-userを作る
cat <<EOF > service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system
EOF
kubectl apply -f service-account.yaml #ユーザをデプロイ
kubectl -n kube-system get secret | grep admin #権限を表示
```

```sh
#トークン取得
kubectl -n kube-system describe secret admin-user-token-n72ds
```

### 3.2. コンテナ起動

コンテナを起動してみます。

[3.2.1. Podのデプロイ](#321-podのデプロイ)
まずは、K8sではマニフェストファイル(YAML)を作成し、それをkubectl applyすることでコンテナをPodという単位で起動(デプロイ)します。
ここで気がつくのが、構築の時点でkubectl applyを使っていたということです。
つまり、K8sクラスターはポッドとサービスにより提供されているということです。

この段階では、クラスタノードからはコンテナにアクセスできるが外部からはアクセスできません。

[3.2.2. サービスのデプロイ](#322-サービスのデプロイ)
外部からアクセスを可能にするために、サービスを追加しノードのポートとPodのポートを接続します。 

#### 3.2.1. Podのデプロイ

1. Podをデプロイします。
2. PodのIPを確認します。
3. 確認したIPに対してアクセスしてみます。`Welcome to nginx!のHTML`が表示されます。
4. ブラウザで`http://{PodのIP}:80`にアクセスしてみます。不能となります。

```sh
# Podをデプロイ
cd /tmp
cat <<EOF > deployments.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
EOF

kubectl apply -f deployments.yml
```

```sh
#PodのIPを確認
kubectl get pod -o wide
```

#### 3.2.2. サービスのデプロイ

1. サービスをデプロイします。
2. `http://10.10.33.72:30036/` にアクセスします。`Welcome to nginx!`のページが表示されます。

```sh
# サービスをデプロイ
cat <<EOF > service.yml
kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: 80
    nodePort: 30036
    protocol: TCP
  selector: # 1Pod複数コンテナの場合は、ココでパケットを振り分ける。
    app: nginx
EOF
kubectl apply -f service.yml
```

## 4. まとめ

上述の手順で以下が達成できました。

- イントラネット環境内でK8sクラスターの構築
- 任意コンテナデプロイ

### 4.1. おさらい

- K8sはコンテナエンジンの上位レイヤのサービスであり、コンテナエンジンが駆動していることは前提条件(今回はDocker)
- K8sを駆動させるのに必要なものは以下
  - kubeadm...K8sクラスターのインストールソフト [公式](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/)
  - kubelet...クラスター内の各ノードで実行されるエージェント [公式](https://kubernetes.io/ja/docs/concepts/overview/components/#kubelet)
  - flannel...K8sクラスタの仮想ネットワークを提供する。
- 任意のコンテナはPodとサービスを使ってデプロイする。

### 4.2. 次のステップ

- ヘルスチェック・冗長性設定
- Master 兼 Worker設定
- イントラネット内コンテナレジストリ・レポジトリとの連携