k8s-the-hard-way in Docker ~ Dockerコンテナ内でkubernetes-the-hard-wayしてみた

# 概要
[kubernetes-the-hard-way](https://github.com/kelseyhightower/kubernetes-the-hard-way/tree/master/docs)  を実践しようと思い、自宅環境に手軽に構築したいためにdockerコンテナ上に構築することを考えました。クラスタ構築の勉強方法の一つと捉えていただければ。
事実誤認や別の効率的なやり方等存じていたら教えていただければ幸いです。

# TL:DR
本稿は、k8sクラスタを手で構築しようとした筆者が詰まったポイントの備忘、もしくは同じくクラスタを構築したいという人がもしかしたら悩むかもしれないポイントの共有を目的としています。
そのため主に詰まったポイントと対処方法、リファレンスについて下記にまとめておきます。
詳細な内容については各章で触れていきます。

|  Issue         | Proposal                |        Reference         |
|:---------------:|:------------------------:|:------------------------:|
|各コンポーネントバイナリの実行時に”Operation not permitted”のエラー|コンテナ実行時にcap_addする|[DockerCapability](https://qiita.com/toshihirock/items/c71b84e96e2bc02fbf5a) [LinuxCapability](http://man7.org/linux/man-pages/man7/capabilities.7.html)|
|SSL証明書関連で実行権がないと怒られる|署名時に該当のhostnameを追加し直す|[本稿参照](https://qiita.com/DaichiSasak1/items/6d23ebd1a82aeed23923#04-certificate-authority)|
|/procや/sysへの書き込み権限がないと怒られる|write権限を付与して該当のFSをマウント、もしくは再マウント|[本稿参照](https://qiita.com/DaichiSasak1/items/6d23ebd1a82aeed23923#compute-instances)|
|containerdバイナリ実行時に`contained Not Found`|依存パッケージ(`libseccomp-dev` `libc6-compat`)のインストール|[ref1](https://kubernetes.io/ja/docs/setup/production-environment/container-runtimes/#containerd), [ref2](https://qiita.com/harashoo/items/77aad3f33bf94901ffb7)|
|コンテナイメージをPullする際に`x509: certificate signed by unknown authority`|`ca-certificates`をインストール|[project page](https://pkgs.alpinelinux.org/package/v3.3/main/x86/ca-certificates)|
|servicesリソースへのアクセスができない|ルーティングを追加|[本稿参照](https://qiita.com/DaichiSasak1/items/6d23ebd1a82aeed23923#11-pod-network-routes)|
|kube-proxyが`write /sys/module/nf_conntrack/parameters/hashsize: operation not supported `|`--conntrack-max=0`を付加|[ref](https://github.com/kinvolk/kube-spawn/issues/14)|
|kubelet起動時の権限不足|tmpfsをマウント|[本稿参照](https://qiita.com/DaichiSasak1/items/6d23ebd1a82aeed23923#compute-instances)|

### はじめに
基本的にgcpの機能は使えないということと、dockerの機能で抑制されている部分を変更する必要がありました。
以下、k8s-the-hard-wayのチュートリアル（以降：本家）に沿ってそれぞれ解説していきます。
構築環境は、VirtualBOX上にCPU:1, RAM:4Gi, Disk:32GiのCentos8をminimalインストールした状態です。centos7でも同様の手順で問題なく動くこと実験済みです。

```bash:環境情報
$ cat /etc/redhat-release 
CentOS Linux release 8.1.1911 (Core) 
$ grep cpu.cores /proc/cpuinfo
cpu cores       : 1
$ lsblk | grep disk
sda           8:0    0   32G  0 disk
$ grep MemTotal /proc/meminfo 
MemTotal:        3871792 kB

$ docker --version
Docker version 19.03.8, build afacb8b
```

## 01-prerequisites 
GoogleクラウドのCLI導入およびアカウント登録。
本稿では不要なので割愛。
## 02-client-tools
SSL証明書系の作成ツール（cfssl, cfssljson）およびkubernetes操作用のCLIツール（kubectl）の導入。
証明書一連の作業は[その他のコマンド](https://kubernetes.io/docs/concepts/cluster-administration/certificates/)でも可能ですが、cfsslは公開鍵通信用の鍵作成から署名要求（CSR: Certificate Signing Request）、CAによる署名までを一気に行うことが可能なので、導入しておくことが好ましいです、素直にインストラクションに従います。
ちなみにcfsslは鍵のjson形式での出力、cfssljsonで鍵ファイルの保存を行います。

```bash:
$ {
curl -O https://storage.googleapis.com/kubernetes-the-hard-way/cfssl/linux/cfssl
curl -O https://storage.googleapis.com/kubernetes-the-hard-way/cfssl/linux/cfssljson
chmod +x cfssl cfssljson
mv cfssl cfssljson /usr/local/bin/
}

# 1.3.4 >= であることを確認
$ cfssl version
Version: 1.3.4
Revision: dev
Runtime: go1.13
$ cfssljson --version
Version: 1.3.4
Revision: dev
Runtime: go1.13
```

次にkubectlを同様にしてインストール。
こちらはkubernetesのいわば神経系とも言うべきkube-apiサーバに対しリクエストをなげるためのバイナリです。
k8sを扱う際、基本全てkube-api経由で管理することになりますが、例えばクラスタ内のワーカーノードの情報を取得したいといった場合に
 `curl -X GET --cacert ../ca/ca.pem --cert admin.pem --key admin-key.pem https://balancer:443/api/v1/nodes`
とGETリクエストを出し、帰ってきたJSON形式の文字列を処理して・・・といったような面倒くさいことになります。しかしこちらのバイナリを使えば 
`kubectl get no`
と、3単語入れるだけで情報の取得から人間に読みやすい形への整形・出力を勝手にしてくれます。
素のAPI経由での操作が気になれば[Reference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#-strong-api-overview-strong-)を読むといいと思います（丸投げ
とてもいい感じにAPIサーバとのやりとりが書かれている記事もありました
　→[転職したらKubernetesだった件](https://qiita.com/yuanying/items/ceeeb7329a4fdc566546)

```bash:kubectl-install
$ {
curl -O https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubectl
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
# 1.15.3 >=
kubectl version --client
}
```

```:output
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.2", GitCommit:"52c56ce7a8272c798dbc29846288d7cd9fbae032", GitTreeState:"clean", BuildDate:"2020-04-16T11:56:40Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
```


## 03-compute-resources
本家ではGCP上にVPCの作成とインスタンスの作成を行なっているが、本稿ではここをDockerコンテナに置き換えます。
なお、ここで作成されるネットワークは以下のような構成になっています。

```
  +---------------------------------------------------+ host machine +----------------------------------------------+
  |                                                                                                                 |
  |                                                                                                                 |
  |                                                 docker network(k8s)                                             |
  |    +-------------------------------------------+ 172.16.10.0/24 +------------------------------------------+    |
  |    |                                                                                                       |    |
  |    |                                                                                                       |    |
  |    |                                                balancer                                               |    |
  |    |                                          +-+ 172.16.10.10 +-+                                         |    |
  |    |                                          |                  |                                         |    |
  |    |                                          +------------------+                                         |    |
  |    |                                                                                                       |    |
  |    |                    ctl-0                         ctl-1                         ctl-2                  |    |
  |    |            +-+ 172.16.10.20 +-+          +-+ 172.16.10.21 +-+          +-+ 172.16.10.22 +-+           |    |
  |    |            |                  |          |                  |          |                  |           |    |
  |    |            +------------------+          +------------------+          +------------------+           |    |
  |    |                                                                                                       |    |
  |    |                  worker-0                      worker-1                      worker-2                 |    |
  |    |           +--+ 172.16.10.30 +--+        +--+ 172.16.10.31 +--+        +--+ 172.16.10.32 +--+          |    |
  |    |           |                    |        |                    |        |                    |          |    |
  |    |           |      podCIDR       |        |      podCIDR       |        |      podCIDR       |          |    |
  |    |           | + 10.200.0.0/24 +  |        | + 10.200.1.0/24 +  |        | + 10.200.2.0/24 +  |          |    |
  |    |           | |               |  |        | |               |  |        | |               |  |          |    |
  |    |           | +---------------+  |        | +---------------+  |        | +---------------+  |          |    |
  |    |           |                    |        |                    |        |                    |          |    |
  |    |           |                    |        |                    |        |                    |          |    |
  |    |           |                    |        | serviceClusterCIDR |        |                    |          |    |
  |    |          +-------------------------------+ 10.100.0.0/16 +----------------------------------+         |    |
  |    |          |                                                                                  |         |    |
  |    |          +----------------------------------------------------------------------------------+         |    |
  |    |           |                    |        |                    |        |                    |          |    |
  |    |           +--------------------+        +--------------------+        +--------------------+          |    |
  |    |                                                                                                       |    |
  |    +-------------------------------------------------------------------------------------------------------+    |
  +-----------------------------------------------------------------------------------------------------------------+
```
### Networking
最初にNetworkingについて説明があるが、ひとまず本チュートリアルの操作に関係のある部分は存在しないので読み流す。
内容的にはkubernetesのネットワーキングの思想は「クラスタ内のpodは一意のIPを持ち、それを通じてノードを問わず互いに認識できる」ことである、というようなことが書いてある。
なお、これもチュートリアルの粋を超えている内容ではあるが、クラスタ内でアクセスコントロールする際はNetworkPolicyリソースを用いる（対応しているPluginをkubelet起動時に指定している必要がある。有名どころは[Calico](https://docs.projectcalico.org/getting-started/kubernetes/)とかでしょうか）

### Firewall Rules
今回CentOS8で構築したため、OSインストールした初期状態ではコンテナ間の通信を許可するために下記の設定がホストマシンで必要です。
CentOS7では特に設定は必要ありませんでした。

```bash:firewall_for_centos8
# コンテナからWANへの通信を許可(apk利用のため)
$ {
nft add rule inet firewalld filter_FWDI_public_allow udp dport 53 accept
nft add rule inet firewalld filter_FWDI_public_allow tcp dport { 80,443 } accept
# コンテナ内部での通信を許可
nft add rule inet firewalld filter_FWDI_public_allow ip saddr 172.16.10.0/24 ip daddr 172.16.10.0/24 accept
}
```
これに関連して格闘した形跡を[こちら](https://qiita.com/DaichiSasak1/items/11687a3c613c8811350c)に記しているので、気が向いたら読んでいただければと思います。
### Kubernetes Public IP Address
Load Balancerに紐づけるIPの取得。
本稿ではコンテナでまとめて作るので省略

### Compute Instances
いよいよ一つの山場。
若干長くなるのと、本家のチュートリアルから外れる内容ではあるので面倒な方は[こちら](https://github.com/DaichiSasak1/BYO_k8snodes_indocker.git)をcloneしていただければそのまま次の04-certificate-authorityに進めます。

ここではLoadBalancer、ControlPlane、WorkerNodeの３種類のイメージを作成し、Dockerサービスを作成していきます。
まずDockerイメージをビルドする際無駄にデーモンが重くならないようにディレクトリを分けます。
テスト目的なのでほとんどの実行バイナリをホスト上から共有するようにしています。

```bash
$ mkdir -p build \
           bin/ctl/bin bin/ctl/exec bin/worker/bin bin/worker/exec bin/worker/opt \
           lib/balancer lib/ctl lib/worker/cni lib/worker/containerd lib/worker/kubelet lib/worker/kube-proxy \
           lib/certs/ca lib/certs/ca_sec lib/certs/ctl lib/certs/worker \
           env/common \
           cert_configs
# こんな感じのディレクトリ構成になります
# .
# |-- bin 
# |   |-- ctl 
# |   |   |-- bin 
# |   |   `-- exec
# |   `-- worker
# |       |-- bin 
# |       |-- exec
# |       `-- opt 
# |-- build
# |-- env 
# |   `-- common
# |-- lib 
# |   |-- balancer
# |   |-- certs
# |   |   |-- ca
# |   |   |-- ca_sec
# |   |   |-- ctl 
# |   |   `-- worker
# |   |       |-- kubelet
# |   |       `-- kube-proxy
# |   |-- ctl 
# |   `-- worker
# |       |-- etccontainerd
# |       |-- kubelet
# |       |-- kube-proxy
# |       `-- varcni
# `-- cert_utils
```
以下の内容でDockerfile、entrypoint.sh（サービス起動用のシェルスクリプト）、docker-composeファイルを作成。インスタンス数は本家と同じくLoadBalancer=1, ControlPlane=3, WorkerNode=3で構成しています。
[DockerFile](https://docs.docker.com/engine/reference/builder/#from)、[docker-compose.yml](https://docs.docker.com/compose/compose-file/#reference-and-guidelines)の各Instructionは本家のページを参考ください。

```bash:build/Dockerfile_ctl
FROM alpine:3.11

RUN apk --update --no-cache --no-progress add \
    bash \
    && rm -rf /var/cache/apk/*

#for ETCD runtime
RUN mkdir -p /etc/etcd /var/lib/etcd 

#for kubernetes control
#such as api-server, conroller-manager, scheduler
RUN mkdir -p /var/lib/kubernetes/

#
COPY entrypoint.sh /
CMD [ "/bin/bash", "/entrypoint.sh" ]
```

```bash:build/Dockerfile_worker
FROM alpine:3.11

RUN apk --update --no-cache --no-progress add \
    bash \
    socat ipset conntrack-tools \
    libseccomp-dev libc6-compat \
    iptables \
    ca-certificates \
    && rm -rf /var/cache/apk/*


# for provisioning worker node (kubelet, proxy)
RUN mkdir -p /etc/cni/net.d \
            /opt/cni/bin \
            /var/lib/kubelet \
            /var/lib/kube-proxy \
            /var/lib/kubernetes \
            /var/run/kubernetes

#
# ENV PATH ${C_HOME}/bin
COPY entrypoint.sh /
CMD [ "/bin/bash", "/entrypoint.sh" ]
```

```bash:build/Dockerfile_loadbalancer
FROM nginx:alpine
RUN printf 'stream {\n\tinclude /etc/nginx/conf.d/*.conf.stream;\n}\n'  >> /etc/nginx/nginx.conf
```
見ての通り全てalpineベースで構成している。
重点だけ記しておくと

- `socat ipset conntrack-tools` はチュートリアル本編に沿って入れる、ネットワーク関連
- `iptables` はkube-proxy動作のために必要だがalpineにデフォルトで入っていないので改めてインストール
- `ca-certificates`は証明書インストールで、これがないとcontainerdがイメージをpullする際に`x509: certificate signed by unknown authority`となってpullできない
- LoadBalancerはnginxの[TCPロードバランサー機能](https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/)を利用します。そのためnginx.confに、別の設定ファイルをhttpブロック外でincludeする行を記載


続けてentrypoint.sh。
単純にkubernetes関連のサービス起動用に作成するシェルスクリプトをスリープを含んでキックしていくだけの代物です。
いろいろ試すためにスクリプトを修正してすぐに反映して、という進め方を想定しているので、openrcなどsysinit系は特に仕込みませんでした（本家からずれてるけど、、）

```bash:build/entrypoint.sh
#!/bin/bash
while read svc sleepSec; do
[ -f ${svc} ] && {
echo " service '$(basename ${svc%.sh})' starting..."
/bin/bash ${svc} &
sleep ${sleepSec:-5}
echo "done."
}
done<<EOF
/exec/ETCD_SERVICE.sh 10
/exec/KUBE-API_SERVICE.sh 20
/exec/KUBE-CONTROLLER-MANAGER_SERVICE.sh
/exec/KUBE-SCHEDULER_SERVICE.sh
/exec/RBAC_AUTH.sh
/exec/CONTAINERD_SERVICE.sh 30
/exec/KUBELET_SERVICE.sh 20
/exec/KUBE-PROXY_SERVICE.sh
EOF

echo "container Ready"
tail -f /dev/null
```
最後にdocker-composeファイル。[LinuxCapability](http://man7.org/linux/man-pages/man7/capabilities.7.html) について勉強する機会となったので最小限のCapabilityとなるように気をつけましたが、ワーカーノードは結局`--privilrged`をつけないと起動できなかった、、

```bash:docker-compose.yml
version: "3.7"                                                                                                                                                                                              
x-services:                                                                                                                                                                                                 
  &default-ctl                                                                                                                                                                                              
    build:                                                                                                                                                                                                  
      context: ./build                                                                                                                                                                                      
      dockerfile: Dockerfile_ctl                                                                                                                                                                            
    image: k8snodes-ctl:0.1                                                                                                                                                                          
    init: true                                                                                                                                                                                              
    networks:                                                                                                                                                                                               
      k8s:                                                                                                                                                                                                  
    volumes:                                                                                                                                                                                                
      - "/sys/fs/cgroup:/sys/fs/cgroup:rw"                                                                                                                                                                                                                                                                                                                                            
      - "./bin/ctl/exec:/exec:ro"                                                                                                                                                                           
      - "./bin/ctl/bin:/usr/local/bin:ro"                                                                                                                                                                   
      - "./lib/certs/ctl:/var/lib/kubernetes:ro"                                                                                                                                                            
      - "./lib/certs/ca:/var/lib/ca:ro"                                                                                                                                                                     
      - "./lib/certs/ca_sec:/var/lib/ca_sec:ro"                                                                                                                                                             
    cap_drop:                                                                                                                                                                                               
      - ALL                                                                                                                                                                                                 
    cap_add:                                                                                                                                                                                                
      - SYS_CHROOT                                                                                                                                                                                          
      - SETGID                                                                                                                                                                                              
      - SETUID                                                                                                                                                                                              
      - CHOWN                                                                                                                                                                                               
      - NET_RAW
      - NET_ADMIN                                                                                                                                                                                          
      - DAC_OVERRIDE                                                                                                                                                                                                                                                                                                                                                                       
    dns_search:
      - k8s
    environment:
      - KUBECONFIG=/var/lib/kubernetes/admin.kubeconfig

x-services:          
  &default-worker        
    build:         
      context: ./build
      dockerfile: Dockerfile_worker
    image: k8snodes-worker:0.1
    init: true     
    networks:      
      k8s:  
    volumes:                                 
      - "/lib/modules:/lib/modules:ro"
      - "./bin/worker/exec:/exec:ro"
      - "./bin/worker/bin:/usr/local/bin:ro"
      - "./bin/worker/opt:/opt/cni/bin:ro"
      - "./lib/worker/etccontainerd:/etc/containerd:ro"
      - "./lib/worker/varcni:/var/cni:ro"
      - "./lib/worker/kubelet:/var/lib/kubelet_config:ro"
      - "./lib/worker/kube-proxy:/var/lib/kube-proxy_config:ro"
      - "./lib/certs/worker/kubelet:/var/lib/kubelet_certs:ro"
      - "./lib/certs/worker/kube-proxy:/var/lib/kube-proxy:ro"
      - "./lib/certs/ca:/var/lib/ca:ro"
      - type: tmpfs
        target: /var/lib/containerd
        tmpfs:              
          size: 500M  
    privileged: true            
    dns_search:                                
      - k8s

x-services:
  &default-balancer
    build:
      context: ./build
      dockerfile: Dockerfile_lb
    image: nginx_lb:alpine
    ports:
      - "10443:443"
    volumes:
      - "./lib/balancer:/etc/nginx/conf.d:ro"
    networks:
      k8s:
    cap_drop:
      - ALL
    cap_add:
      - NET_RAW
      - NET_BIND_SERVICE
      - CHOWN
      - SETGID
      - SETUID

services:
  balancer:
    << : *default-balancer
    container_name: balancer
    hostname: balancer
    networks:
      k8s:
        ipv4_address: 172.16.10.10
  ctl-0:
    << : *default-ctl
    container_name: ctl-0
    hostname: ctl-0
    networks:
      k8s:
        ipv4_address: 172.16.10.20
  ctl-1:
    << : *default-ctl
    container_name: ctl-1
    hostname: ctl-1
    networks:
      k8s:
        ipv4_address: 172.16.10.21
  ctl-2:
    << : *default-ctl
    container_name: ctl-2
    hostname: ctl-2
    networks:
      k8s:
        ipv4_address: 172.16.10.22

  worker-0:
    << : *default-worker
    container_name: worker-0
    hostname: worker-0
    networks:
      k8s:
        ipv4_address: 172.16.10.30
  worker-1:
    << : *default-worker
    container_name: worker-1
    hostname: worker-1
    networks:
      k8s:
        ipv4_address: 172.16.10.31
  worker-2:
    << : *default-worker
    container_name: worker-2
    hostname: worker-2
    networks:
      k8s:
        ipv4_address: 172.16.10.32

networks:
  k8s:
    name: k8s 
    ipam:
      config:
        - subnet: 172.16.10.0/24
    driver_opts:
          com.docker.network.bridge.name: k8s_br
```
これも要点だけまとめると

- `x-`接頭辞は[Extensionフィールド](https://docs.docker.com/compose/compose-file/#extension-fields)であり、記述はできるが解釈されなくなるので、yamlのアンカー・エイリアス構文を用いることができるようになる
- `/sys/fs/cgroup:/sys/fs/cgroup:rw`の権限でのマウントは必須だが、workerではprivilegedオプションをつけているので省略可
- workerノードの`/lib/modules:/lib/modules:ro`はkube-proxyに求められるが、無視しても問題ないとは言われるので必須ではない
- workerノードのcontainerdのルートディレクトリに`tmpfs`をマウントは、デフォルトのoverlayfsではエラーとなるため必須
- dns-searchは互いのノードIPを調べる際に必要となる。＜dokcer networkの名前＞というドメインに設定されるので、そこに合わせるようにする。
- docker-composeのバージョン3系を用いる場合、`init: true`の部分に関しては`3.7>=`となっている。ただしこのオプションを付けなくても特に問題はない。ただしExtensionフィールドは`3.4>=` となっているのでそこだけ注意

以上です。少々長くなりましたが、これでkubernetesの各コンポーネントを動かす準備ができました。
一応本家ではsshで各インスタンスにアクセスするようになっていますが、ここではdockerなのでプロセスのアタッチで各ノードに入ります。

```bash:ノードへのアクセス方法
$ docker exec -ti <container name> sh
```


## 04-certificate-authority
kubernetesの各コンポーネントはクラウド基板上で稼働することが前提であり、互いの接続をTLS認証で行います。
このセクションでは自分用[PKI](https://en.wikipedia.org/wiki/Public_key_infrastructure)を作成して自己署名した証明書を作ります。
まずはCAを作ります。

```bash:CA
$ cd cert_util
$ {
cat > ca-config.json <<EOF
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "kubernetes": {
        "usages": ["signing", "key encipherment", "server auth", "client auth"],
        "expiry": "8760h"
      }
    }
  }
}
EOF

cat > ca-csr.json <<EOF
{
  "CN": "Kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "JP",
      "L": "Tokyo",
      "O": "Kubernetes",
      "OU": "CA",
      "ST": "Asia"
    }
  ]
}
EOF
# C  : Country
# L  : Locality or City
# O  : Organization
# OU : Organization Unit
# ST : State


cfssl gencert -initca ca-csr.json | cfssljson -bare ca

}
```
CAの公開鍵認証用のペアができます。

```bash
ca-key.pem
ca.pem
```
ここからはkubernetesクラスタのサーバの各コンポーネントと、kubectlを扱うクライアントのための証明書を作成し、このCAによって署名していきます。
各証明書の役割は以下の通りです

- adminユーザ用 (controlプレーン＆クライアント)
- kubelet用 (workerノード)
- kube-proxy用 (workerノード)
- Controller-Manager用 (controlプレーン)
- Scheduler用 (controlプレーン)
- Service-Account-Key用 (controlプレーン)
- kubernetes-API-server(兼etcd)用 (controlプレーン)


Service Account Keyに関しては[managing service accounts](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/)に記載ありますが、Contoroller Managerとkube-api-serverコンポーネントで用いられ、各podのプロセスがデフォルトの状態でkube-api-serverにアクセスする権限を持たせるために使われる。ということらしいです。
まずはadminユーザ用。これはcontrolプレーン、及び自分がクラスタと更新する際にkubectlから呼び出して使う用の証明書になります。

```bash:adminユーザ用
# in ./cert_util
$ sed '
/"CN"/s/Kubernetes/admin/
/"O"/s/Kubernetes/system:masters/
/"OU"/s/CA/Kubernetes The Hard Way/
' ca-csr.json | tee admin-csr.json

$ cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  admin-csr.json | cfssljson -bare admin

```
上と同じく、adminと頭についた鍵ペアができます。
`*.csr`のファイルはcaで署名される前の署名要求なので、基本鍵ペア以外は不要です。
次にworkerノードの各コンポーネント用の証明書ペアを作成していきます。

kubeletではNode Authorizerという認証モードを用いてkubeletからのAPIリクエストを認証するようです。これを用いるためには 「`system:nodes` グループ」に属し、「`system:node:<nodeName>`というユーザ名」を持った証明書をkubeletが用いる必要があるようです。[公式のドキュメント](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#kubernetes-signers)にもしっかりと記載がありました。
x509公開鍵認証ではCommonName(CN)フィールドの値が「ユーザ名」、Organization（O)フィールドの値が「グループ」となるようなのでここの整合性がちゃんと取れるように気をつけます。ちなみにnodeNameはworkerノード内で`hostname`で帰ってくる値と一致している必要がありました。

```bash:kubelet用
# in ./cert_util

$ for instance in worker-0 worker-1 worker-2
do sed '
/"CN"/s/admin/system:node:'${instance}'/
/"O"/s/system:masters/system:nodes/
' admin-csr.json | tee ${instance}-csr.json

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=${instance} \
  -profile=kubernetes \
  ${instance}-csr.json | cfssljson -bare ${instance}
done
```
`-hostname`で証明書のSAN (Subject Altenative Name)を設定できます。この証明書を使って接続するリモート側のDNS名、もしくはアドレスが、CNもしくはSANに設定されている値に一致しないと証明書エラーとなってしまいます。
実際に試したところkubectlを用いてpodにアタッチする際、kubeletは自身に対してAPIリクエストを出しており、ここで`-hostname`を指定しなければ`kubectl exec`や`kubectl logs`を実行しようとする際に証明書エラーとなりました。
本家ではworkerノード自体のInternal・External IPを指定していましたが、特に指定しなくでも（今のところは）動かせたので確認できたケースではDNS名しか要求していない模様。
問題生じたらアップデートしようかと思います。

長くなりましたが次にkube-proxy用の証明書。
ここからは特に意識することはなく本家通り粛々と作成していきます。

```bash:kube-proxy用
# in ./cert_util

$ sed '
/"CN"/s/admin/system:kube-proxy/
/"O"/s/system:masters/system:node-proxier/
' admin-csr.json | tee kube-proxy-csr.json

$ cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  kube-proxy-csr.json | cfssljson -bare kube-proxy
```

次にkube-controller-manager用の証明書。一応、ここからはcontrolプレーンの各コンポーネントになります。

```bash:kube-controller-manager用
# in ./cert_util

$ sed '
/"CN"/s/admin/system:kube-controller-manager/
/"O"/s/system:masters/system:kube-controller-manager/
' admin-csr.json | tee kube-controller-manager-csr.json

$ cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  kube-controller-manager-csr.json | cfssljson -bare kube-controller-manager
```
kube-scheduler用

```bash:kube-scheduler用
# in ./cert_util

$ sed '
/"CN"/s/admin/system:kube-scheduler/
/"O"/s/system:masters/system:kube-scheduler/
' admin-csr.json | tee kube-scheduler-csr.json

$ cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  kube-scheduler-csr.json | cfssljson -bare kube-scheduler
```
service-account用

```bash:service-account用
# in ./cert_util

$ sed '
/"CN"/s/admin/service-accounts/
/"O"/s/system:masters/Kubernetes/
' admin-csr.json | tee service-account-csr.json

$ cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  service-account-csr.json | cfssljson -bare service-account
```
最後にkubernetes-api-server用なのですが、こいつは最低限etcd達とLoad Balancer、あとkubernetesが自動的に作成するサービスリソースのClusterIP(後ほど割り当てるサービスCIDRの、一番最初の序列のIP。CIDRが10.100.0.0/16なら10.100.0.1となる)につながれば動作できたので、そこにSANを限定していきます（本家ではデフォルトで作成されるサービスリソース(kubernetes)のいろいろな形でのドメイン含めた名前が加えられており、そこから外れているが最低限の設定の方が不具合発生時した際に勉強になる部分が多いと感じるので、、）

```bash:API_&_etcd用
# in ./cert_util

$ sed '
/"CN"/s/admin/kubernetes/
/"O"/s/system:masters/Kubernetes/
' admin-csr.json | tee kubernetes-csr.json

$ {
LOs='localhost,127.0.0.1'
# Control Plane
CTLs='172.16.10.20,172.16.10.21,172.16.10.22'
# kubernetes services
SVC='10.100.0.1,kubernetes,balancer'
# 外部のクライアントからアクセスする用の、dockerホスト
EXTERNAL='172.16.10.1'


cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=${CTLs},${LOs},${SVC},${EXTERNAL} \
  -profile=kubernetes \
  kubernetes-csr.json | cfssljson -bare kubernetes
}

```
最後にしかるべき場所にこれらの証明書を配置します。
> `kube-proxy`, `kube-controller-manager`, `kube-scheduler`, and `kubelet`

は
次の段階でkubeconfigファイルに含めて用いるので（あと一応、`admin`も）、これらは動かしません。

```bash
# in ./cert_util
$ {
cp -p ca.pem ../lib/certs/ca
mv ca-key.pem ../lib/certs/ca_sec
mv service-account*.pem ../lib/certs/ctl
mv kubernetes*.pem ../lib/certs/ctl
}
```

## 05-kubernetes-configuration-files
ここではkubernetes APIのクライアントとなる
> `controller manager`, `kubelet`, `kube-proxy`, and `scheduler` clients and the `admin` user.

のための、接続のための認証情報と接続先の情報をkubeconfigファイルという物の中に埋め込んでいきます。
クライアント達の接続先はローカルホスト、及びLoad Balancerとなるので、Load BalancerのIPを本家では取得しています。本稿ではdocker-compose.ymlの中で定めた`172.16.10.10`です。

```bash
$ KUBERNETES_PUBLIC_ADDRESS='balancer'
```
それでは作成していきます。


```bash:kubelet用
# in ./cert_util
$ {
for instance in worker-0 worker-1 worker-2; do
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:443 \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-credentials system:node:${instance} \
    --client-certificate=${instance}.pem \
    --client-key=${instance}-key.pem \
    --embed-certs=true \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:node:${instance} \
    --kubeconfig=${instance}.kubeconfig

  kubectl config use-context default --kubeconfig=${instance}.kubeconfig
done
}
```

```bash:kube-proxy用
# in ./cert_util
$ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:443 \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config set-credentials system:kube-proxy \
    --client-certificate=kube-proxy.pem \
    --client-key=kube-proxy-key.pem \
    --embed-certs=true \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:kube-proxy \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig
}
```

```bash:kube-controller-manager用
# in ./cert_util
$ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:6443 \
    --kubeconfig=kube-controller-manager.kubeconfig

  kubectl config set-credentials system:kube-controller-manager \
    --client-certificate=kube-controller-manager.pem \
    --client-key=kube-controller-manager-key.pem \
    --embed-certs=true \
    --kubeconfig=kube-controller-manager.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:kube-controller-manager \
    --kubeconfig=kube-controller-manager.kubeconfig

  kubectl config use-context default --kubeconfig=kube-controller-manager.kubeconfig
}
```

```bash:kube-scheduler用
# in ./cert_util
$ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:6443 \
    --kubeconfig=kube-scheduler.kubeconfig

  kubectl config set-credentials system:kube-scheduler \
    --client-certificate=kube-scheduler.pem \
    --client-key=kube-scheduler-key.pem \
    --embed-certs=true \
    --kubeconfig=kube-scheduler.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:kube-scheduler \
    --kubeconfig=kube-scheduler.kubeconfig

  kubectl config use-context default --kubeconfig=kube-scheduler.kubeconfig
}
```

```bash:adminユーザ用
# in ./cert_util
$ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:6443 \
    --kubeconfig=admin.kubeconfig

  kubectl config set-credentials admin \
    --client-certificate=admin.pem \
    --client-key=admin-key.pem \
    --embed-certs=true \
    --kubeconfig=admin.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=admin \
    --kubeconfig=admin.kubeconfig

  kubectl config use-context default --kubeconfig=admin.kubeconfig
}
```

最後にこれらもしかるべき場所に配置します。一応、kubelet用の`worker-?.kubeconfig`は本来それぞれのノードにあるべきですが特にそこまで拘らなくてもいいかと思いひとところにまとめています。

```bash
# in ./cert_util
$ {
mv admin.kubeconfig kube-scheduler.kubeconfig kube-controller-manager.kubeconfig ../lib/certs/ctl/

mv kube-proxy.kubeconfig ../lib/certs/worker/kube-proxy/
mv worker-?.kubeconfig ../lib/certs/worker/kubelet/
mv worker-*.pem ../lib/certs/worker/kubelet/
}
```

## 06-data-encryption-keys
ここではKubernetes上で扱われるデータ（APIリソース）の暗号化を行う設定ファイルを作成します。
ここで作成するファイルに関して言えば、APIリソースの中でもsecretsリソースに対して暗号化するようにしています。`13-smoke-test`で動作確認します。

```bash
$ cd ..
# in .
$ cat | tee lib/certs/ctl/encryption-config.yaml <<EOF
kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: $(head -c 32 /dev/urandom | base64)
      - identity: {}
EOF
```

## 07-bootstrapping-etcd
ここではconrolプレーンのデータストアであるetcdを起動させます。
各controlプレーン内で起動させ、３台でクラスタ構成を組みます。
まず、`etcd`と`etcdctl`のバイナリを入手します

```bash
# in .
$ {
curl -LO "https://github.com/etcd-io/etcd/releases/download/v3.4.0/etcd-v3.4.0-linux-amd64.tar.gz"
# 最新版は https://github.com/etcd-io/etcd/releases をチェック
tar -xf etcd-v3.4.0-linux-amd64.tar.gz

mv etcd-v3.4.0-linux-amd64/etcd* bin/ctl/bin/
# controlプレーンで/usr/local/binにマウントする領域。移動したもの以外は削除してよし
rm -fr etcd-v3.4.0-linux-amd64*
ls -lh bin/ctl/bin/
}
```
etcd起動用のシェルを書きます。
それぞれのホスト名とdockerから割り当てられているIPを動的に入手するようにしています。
それと、証明書類について前章で作成した`kubernetes*.pem`が必要になりますが、本編での設定と合わせるためシンボリックリンク貼るようにしています。

```bash:bin/ctl/exec/ETCD_SERVICE.sh
#!/bin/bash
ETCD_NAME=$(hostname -s)
INTERNAL_IP=$(awk '/'${ETCD_NAME}'/ {print $1}' /etc/hosts)
CTLs=$(IFS=',';for i in {0..2};do CTLs[$i]=ctl-${i}=https://172.16.10.2${i}:2380;done;echo "${CTLs[*]}"; )

for link_target in /var/lib/kubernetes/kubernetes.pem /var/lib/kubernetes/kubernetes-key.pem /var/lib/ca/ca.pem; do                                                                                        
  dest="/etc/etcd/$(basename ${link_target})"                                                                                                                                                                
  [ -L  ${dest} ] || ln -s ${link_target} ${dest}                                                                                                                                                            
done

/usr/local/bin/etcd \
  --name ${ETCD_NAME} \
  --cert-file=/etc/etcd/kubernetes.pem \
  --key-file=/etc/etcd/kubernetes-key.pem \
  --peer-cert-file=/etc/etcd/kubernetes.pem \
  --peer-key-file=/etc/etcd/kubernetes-key.pem \
  --trusted-ca-file=/etc/etcd/ca.pem \
  --peer-trusted-ca-file=/etc/etcd/ca.pem \
  --peer-client-cert-auth \
  --client-cert-auth \
  --initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \
  --listen-peer-urls https://${INTERNAL_IP}:2380 \
  --listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://${INTERNAL_IP}:2379 \
  --initial-cluster-token etcd-cluster-0 \
  --initial-cluster ${CTLs} \
  --initial-cluster-state new \
  --log-outputs '/etc/etcd/etcd.log' \
  --logger=zap \
  --data-dir=/var/lib/etcd
```
オプション名でなんとなく判別できますが、一応要点だけまとめると

- etcdはデフォルトでクラスタ間用の通信にポート番号`2380`を、クライアント用に`2379`を用いる（もちろん変更可）
- peerでクラスタ間の通信について定義
- clientで外部からのリクエストを受け付ける通信について定義。ローカルを入れてるのはデバッグ時にetcdctlを用いるが、そのデフォルトの向き先がそうなっているため
- clientからの通信の際は`--key`と`--cert`で指定した証明書類と適合するものが必要になる
- `--log-outputs`は本家では指定されていない。おそらくsysinitで起動するためと思われるが、ここでは明示的に指定して出力させておく


次に動作確認です。
これだけ作成した状態でいったんサービスを起動させます。

```bash
# in .
$ docker-compose up -d ctl-{0..2}
Creating network "k8s" with the default driver
Creating ctl-2 ... done
Creating ctl-0 ... done
Creating ctl-1 ... done

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
ed27920de960        k8snodes-ctl:0.1    "/bin/bash /entrypoi…"   47 seconds ago      Up 44 seconds                           ctl-1
3e39308fc506        k8snodes-ctl:0.1    "/bin/bash /entrypoi…"   47 seconds ago      Up 44 seconds                           ctl-0
5ca113b3596e        k8snodes-ctl:0.1    "/bin/bash /entrypoi…"   47 seconds ago      Up 44 seconds                           ctl-2

$ docker exec -ti ctl-0 /bin/sh -c "ETCDCTL_API=3 etcdctl member list \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/etcd/ca.pem \
  --cert=/etc/etcd/kubernetes.pem \
  --key=/etc/etcd/kubernetes-key.pem -w table"

```

```:output
+------------------+---------+-------+---------------------------+---------------------------+------------+
|        ID        | STATUS  | NAME  |        PEER ADDRS         |       CLIENT ADDRS        | IS LEARNER |
+------------------+---------+-------+---------------------------+---------------------------+------------+
| 61c4f7534361ad48 | started | ctl-1 | https://172.16.10.21:2380 | https://172.16.10.21:2379 |      false |
| b167b173b818a05c | started | ctl-0 | https://172.16.10.20:2380 | https://172.16.10.20:2379 |      false |
| f6d332cd022ef5f8 | started | ctl-2 | https://172.16.10.22:2380 | https://172.16.10.22:2379 |      false |
+------------------+---------+-------+---------------------------+---------------------------+------------+
```

ちゃんと表示されば成功です。
失敗したら、大抵の場合はdockerホスト側のNetfilterの設定がうまくできていない可能性が高いです。

ここまで確認できたらいったん止めます。

```bash
$ docker-compose stop
```
## 08-bootstrapping-kubernetes-controllers
次にcontrolプレーンの他のコンポーネントを起動していきます。
まずコンポーネント用のバイナリを入手し、共有領域に置いてあげます。

```bash
# in .
$ {
curl -L \
-O  "https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kube-apiserver" \
-O  "https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kube-controller-manager" \
-O  "https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kube-scheduler" \
-O  "https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kubectl"

chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl
mv kube-apiserver kube-controller-manager kube-scheduler kubectl bin/ctl/bin
}
```

続けてそれぞれのサービスを起動するためのスクリプトを書いていきます。

APIサーバ

```bash:bin/ctl/exec/KUBE-API_SERVICE.sh
#!/bin/bash

INTERNAL_IP=$(awk '/'$(hostname -s)'/ {print $1}' /etc/hosts)
CTLs=$(IFS=',';for i in {0..2};do CTLs[$i]=https://172.16.10.2${i}:2380;done;echo "${CTLs[*]}"; )

# add route to other node's pods
# this config required in "11-pod-network-routes" section
for ((i=0;i<3;i++));do
  ip r add to 10.200.$i.0/24 via 172.16.10.3$i
done

LOGFILE='/var/log/kube-api.log'

/usr/local/bin/kube-apiserver \
  --advertise-address=${INTERNAL_IP} \
  --allow-privileged=true \
  --apiserver-count=3 \
  --audit-log-maxage=30 \
  --audit-log-maxbackup=3 \
  --audit-log-maxsize=100 \
  --audit-log-path=/var/log/audit.log \
  --authorization-mode=Node,RBAC \
  --bind-address=0.0.0.0 \
  --client-ca-file=/var/lib/ca/ca.pem \
  --enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \
  --etcd-cafile=/var/lib/ca/ca.pem \
  --etcd-certfile=/var/lib/kubernetes/kubernetes.pem \
  --etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \
  --etcd-servers=${CTLs} \
  --event-ttl=1h \
  --encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \
  --kubelet-certificate-authority=/var/lib/ca/ca.pem \
  --kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \
  --kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \
  --kubelet-https=true \
  --runtime-config=api/all=true \
  --service-account-key-file=/var/lib/kubernetes/service-account.pem \
  --service-cluster-ip-range=10.100.0.0/16 \
  --service-node-port-range=30000-32767 \
  --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \
  --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \
  --log-file=${LOGFILE} \
  --logtostderr=false \
  --v=2
```
- ここでもログファイルを明示的に指定する部分を追加しています。
- APIリソースの中のserviceリソースが割り当てられるCIDRもここで指定しています。Controller-Managerの起動オプションの中でも指定できますが、こちらで設定した内容の方が優先されました

Controller-Manager

```bash:bin/ctl/exec/KUBE-CONTROLLER-MANAGER_SERVICE.sh
#!/bin/bash
LOGFILE="/var/log/kube-controller-manager.log"

/usr/local/bin/kube-controller-manager \
  --bind-address=0.0.0.0 \
  --cluster-cidr=10.200.0.0/16 \
  --cluster-name=kubernetes \
  --cluster-signing-cert-file=/var/lib/ca/ca.pem \
  --cluster-signing-key-file=/var/lib/ca_sec/ca-key.pem \
  --kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \
  --leader-elect=true \
  --root-ca-file=/var/lib/ca/ca.pem \
  --service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \
  --use-service-account-credentials=true \
  --log-file=${LOGFILE} \
  --logtostderr=false \
  --v=2

```
kube-Scheduler
ここだけConfigファイルでパラメータを渡します。
基本k8sではConfigファイルを経由してオプション類を渡すことが推奨されています。
APIとController-ManagerだけなぜかConfigファイルを渡すためのオプション等がなくベタ書きという感じでした。コアモジュールだからかわかりませんが、そのうち設定できるようになるのかもしれません。

```bash:kube-scheduler.yaml
# in .
$ cat <<EOF | sudo tee lib/certs/ctl/kube-scheduler.yaml
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig"
leaderElection:
  leaderElect: true
EOF
```

```bash:bin/ctl/exec/KUBE-SCHEDULER_SERVICE.sh
#!/bin/bash
CONFIGFILE='/var/lib/kubernetes/kube-scheduler.yaml'
LOGFILE='/var/log/kube-scheduler.log'

/usr/local/bin/kube-scheduler \
  --config=${CONFIGFILE} \
  --log-file=${LOGFILE} \
  --logtostderr=false \
  --v=2
```
本家ではここで外部のロードバランサーが行うヘルスチェックのため、contarolプレーンの80番ポートに対するリクエストを、kube-api-serverが公開する`https://127.0.0.1:6443/healthz`へと中継させるようにnginxのproxy機能を活用する設定をしています。
が、本稿で用いているロードバランサーはフリー版のnginxで代用しており、[製品バージョンでしかActiveヘルスチェック機能は提供されない](https://docs.nginx.com/nginx/admin-guide/load-balancer/http-health-check/#active-health-checks)ようでしたので、やるとしたらPassiveですがここまでマネなくてもいいかと思い割愛しました。
最後にkube-api-serverから各workerノードのkubeletにRBACでアクセスするための設定をします。
これはpodのログを取得する際やコマンドを実行する際に用いられます。
後ほどpodは起動するけど上記のような操作ができないといった場合にはこちらのClusterRoleリソースがちゃんと作られているかどうか確認するといいと思います。
こちらはAPIサーバで管理されるリソース作りなので、一度作成すればクラスタ全体で有効になりますが、特に何度適用しても問題はないのでサービス起動時のスクリプトに含めています。
ちなみにこの際にadminユーザのkubeconfigが用いられるようになっています。

```bash:bin/ctl/exec/RBAC_AUTH.sh
#!/bin/bash
CONFIGFILE='/var/lib/kubernetes/admin.kubeconfig'

cat <<EOF | kubectl apply --kubeconfig ${CONFIGFILE} -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdata: "true"
  labels:
    kubernetes.io/bootstrapping: rbac.defaults
  name: system:kube-apiserver-to-kubelet
rules:
  - apiGroups:
      - ""
    resources:
      - nodes/proxy
      - nodes/stats
      - nodes/log
      - nodes/spec
      - nodes/metrics
    verbs:
      - "*"
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: system:kube-apiserver
  namespace: ""
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:kube-apiserver-to-kubelet
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: kubernetes
...
EOF
```
- kube-api-serverは`--kubelet-client-certificate`で指定した証明書で持って、`kubernetes`ユーザとしてkubeletにアクセスしにいく
- そのため作成したClusterRoleリソースを`kubernetes`ユーザに紐づけるClusterRoleBindingリソースを作成している

API起動時に上記のオプションで指定した証明書（kubernetes.pem）のCNはkubernetesでした。
それをユーザ名としてリクエストが正当かどうか（resourceに対してverbを行っても良いか）Authorizeするための設定ということですね。
ここまできたところで、Controlプレーンのコンポーネントは全て起動するようになっているはずなので、いったんサービスを起動しヘルスチェックを行います。

```bash
$ docker-compose up --force-recreate -d ctl-{0..2}
$ docker-compose logs -f
# 'container Ready'が表示されるまで待ちます。<C-c>
$ docker exec -ti ctl-0 sh

# in ctl-0 container
$ kubectl get cs
NAME                 STATUS    MESSAGE             ERROR
controller-manager   Healthy   ok                  
scheduler            Healthy   ok                  
etcd-0               Healthy   {"health":"true"}   
etcd-2               Healthy   {"health":"true"}   
etcd-1               Healthy   {"health":"true"} 

$ kubectl get clusterRole | grep apiserver-to-
system:kube-apiserver-to-kubelet                                       2020-06-14T05:51:06Z
$ kubectl get clusterRoleBinding -o wide| grep apiserver-to-
system:kube-apiserver                                  10m   ClusterRole/system:kube-apiserver-to-kubelet                       kubernetes  
# 上記のような出力があれば正常に動作している
# 'get cs'の結果は帰ってくるけどclusterRole関連が帰ってこない場合はリソースの関係で起動が遅れているので、下記のコマンドで改めて登録し直す
$ bash /exec/RBAC_AUTH.sh
# 確認できたらホストマシンに戻ります。
$ exit
```

このセクションの最後にロードバランサーにルールを追加し、正常にバランシングが行われているか確かめています。
本稿ではここをnginx用の設定に置き換えています。

```bash:
# in .
$ cat <<EOF | tee lib/balancer/lb.conf.stream
upstream k8s_ctl {
  server ctl-0:6443;
  server ctl-1:6443;
  server ctl-2:6443;
}
server {
  listen 443;
  proxy_pass k8s_ctl;
}
EOF

$ docker-compose up -d balancer
Creating balancer ... done

# dockerのホストマシンの10443番ポートに紐づけられていることを確認
$ docker port balancer
# 疎通確認
$ curl --cacert lib/certs/ca/ca.pem https://127.0.0.1:10443/version
{                                                                                                                                                                                                           
  "major": "1",
  "minor": "18",
  "gitVersion": "v1.18.1",
  "gitCommit": "7879fc12a63337efff607952a323df90cdc7a335",
  "gitTreeState": "clean",
  "buildDate": "2020-04-08T17:30:47Z",
  "goVersion": "go1.13.9",
  "compiler": "gc",
  "platform": "linux/amd64"
}
```
証明書類を指定しないアクセスは`system:anonymous`ユーザと解釈されAuthorizationで弾かれますが、/versionは取得できるようです。
上記のような出力が帰ってくれば正常にバランシングできていることになります。

長かったですがこれでやっとcontrolプレーンが動くようになりました。
次のセクションでworkerノードを動かせばひとまずクラスタとしては動くようになりますのでもうひと頑張りです。

## 09-bootstrapping-kubernetes-workers
ここではWorkerノードを作成していきます。
最初に依存パッケージを入れていますが、これはDockerfileで入れているため特に操作は不要です。
次にswappingをオフにすることがデフォルトで求められます。
Dockerの場合はホストマシンのシステムファイルを共有するため、ホストマシン（か、`--priviledged`しているのでworkerからでもいいですが）にて `swapoff`しないとデフォルト設定のkubeletの起動に失敗します。
が、本稿ではそれほど本格的に運用することを目的としているわけではなく、もしswappingがが起きてもそれほど深刻なことにはならないので、`swapoff`にせずにkubeletの設定の方を変えます。

まず、Workerノード用のバイナリをダウンロードし、展開します。
kubectlも再度ダウンロードしているのですが、workerノードからの使いどころがよくわからなかったので省略しています。

```bash
# in .
$ curl -L \
-O  https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.18.0/crictl-v1.18.0-linux-amd64.tar.gz \
-O  https://github.com/opencontainers/runc/releases/download/v1.0.0-rc8/runc.amd64 \
-O  https://github.com/containernetworking/plugins/releases/download/v0.8.2/cni-plugins-linux-amd64-v0.8.2.tgz \
-O  https://github.com/containerd/containerd/releases/download/v1.2.13/containerd-1.2.13.linux-amd64.tar.gz \
-O  https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kube-proxy \
-O  https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kubelet

$ {
tar -xf containerd-1.2.13.linux-amd64.tar.gz -C bin/worker/
tar -xf cni-plugins-linux-amd64-v0.8.2.tgz -C bin/worker/opt/
tar -xf crictl-v1.18.0-linux-amd64.tar.gz -C bin/worker/bin/
mv runc.amd64 runc
chmod +x kube-proxy kubelet runc
mv kube-proxy kubelet runc bin/worker/bin
rm -f cni-plugins-linux-amd64-v0.8.2.tgz crictl-v1.18.0-linux-amd64.tar.gz containerd-1.2.13.linux-amd64.tar.gz
}
```
次にcniの設定を行います。
このチュートリアルではコンテナランタイムとしてcontainerdを直接用いていますが、こいつがpodを作る際にこちらのcniを用いてネットワークデバイスを割り当てます。
kubeletではなく自分の手でcniを使ってネットワークデバイスを作成するといった試みはネットワーク上探すと結構出てきますが、大元の使い方は[こちら](https://github.com/containernetworking/cni/blob/master/SPEC.md)にありますので、やってみると余計kubeletのありがたみを感じるかもしれません（知らんけど）
設定にあたり、`ipam`の`host-local`では、ここで定義したレンジからpodにアドレスを割り当てます。あくまでkubeletが管理しており、kube-api-serverは「こういうpod作ったよ」情報を保持するのみです。
つまり、ここで渡すCIDRが被ってしまうと同じIPを持つpodがたくさんできてしまいアクセスできなくなるので、workerノードごとにCIDRレンジを変更する必要があります。
そのように動的に設定するため、まずは各workerノードコンテナ内の/var/cniに一時的に設定ファイルを起き、containerd起動のスクリプト内で置換して本来の場所に移すように作ります。

```bash
# in .
$ cat <<EOF | sudo tee lib/worker/varcni/10-bridge.conf
{
    "cniVersion": "0.3.1",
    "name": "bridge",
    "type": "bridge",
    "bridge": "cnio0",
    "isGateway": true,
    "ipMasq": true,
    "ipam": {
        "type": "host-local",
        "ranges": [
          [{"subnet": "\${POD_CIDR}"}]
        ],
        "routes": [{"dst": "0.0.0.0/0"}]
    }
}
EOF

$ cat <<EOF | sudo tee lib/worker/varcni/99-loopback.conf
{
    "cniVersion": "0.3.1",
    "name": "lo",
    "type": "loopback"
}
EOF
```
次にcontainerdを起動するスクリプトを書いていきます。
containerdは外部から設定ファイルを読み込みます。
ファイルにて設定できる項目は`containerd config default`コマンドで確認できます。

```bash
$ cat << EOF | tee lib/worker/etccontainerd/config.toml
[plugins]
  [plugins.cri.containerd]
    snapshotter = "overlayfs"
    [plugins.cri.containerd.default_runtime]
      runtime_type = "io.containerd.runtime.v1.linux"
      runtime_engine = "/usr/local/bin/runc"
      runtime_root = ""
EOF
```

```bash:bin/worker/exec/CONTAINERD_SERVICE.sh
#!/bin/bash
LOGFILE="/var/log/containerd.log" 
cp -p /var/cni/* /etc/cni/net.d/
POD_CIDR="10.200.${HOSTNAME#worker-}.0/24"
CNICONF='/etc/cni/net.d/10-bridge.conf'

sed -i 's|${POD_CIDR}|'${POD_CIDR}'|' ${CNICONF}
/usr/local/bin/containerd \
  --config /etc/containerd/config.toml >>${LOGFILE} 2>&1
```
次にkubeletの起動スクリプト
workerノード内のバイナリは基本設定ファイルを読み込ませる形で起動します。
まずは設定ファイルの中身です。ここでもノードごとに動的に変えたい部分が存在しますので、kubelet起動時に置き換えるようにしています。
本家ではここでもPODCIDRを指定していますが、これはstandaloneモードでのみ用いられるもののようです。今回はクラスタモードで起動するので特に指定する必要はありません。さらに言えばhost-localなcniを用いるのでMasterノードで行うべき設定すらも関係ありません。
それと、各ノードで実行されるpodへの通信を確保するためにルーティングテーブルを作成する必要があります。そのルーティングも動的に実行するように設定しています。
まずは設定ファイルです。

```bash
# in .
$ cat <<EOF |  tee lib/worker/kubelet/kubelet-config.yaml
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
  anonymous:
    enabled: false
  webhook:
    enabled: true
  x509:
    clientCAFile: "/var/lib/ca/ca.pem"
authorization:
  mode: Webhook
clusterDomain: "cluster.local"
clusterDNS:
  - "10.100.0.10"
# resolvConf: "/run/systemd/resolve/resolv.conf"
runtimeRequestTimeout: "15m"
tlsCertFile: "/var/lib/kubelet_certs/\${HOSTNAME}.pem"
tlsPrivateKeyFile: "/var/lib/kubelet_certs/\${HOSTNAME}-key.pem"
failSwapOn: false
EOF
```
起動スクリプト

```bash:bin/worker/exec/KUBELET_SERVICE.sh
#!/bin/bash
CONFIGFILE="/var/lib/kubelet-config.yaml"                                                                                                                                                    
LOGFILE="/var/log/kubelet.log"

NodeNum=${HOSTNAME#worker-}

sed -e '
  s|${HOSTNAME}|'${HOSTNAME}'|g
' /var/lib/kubelet_config/kubelet-config.yaml > ${CONFIGFILE}

# add route to other node's pods
# this config required in "11-pod-network-routes" section
for ((i=0;i<3;i++));do
 [ $i -ne ${NodeNum} ] && ip r add to 10.200.$i.0/24 via 172.16.10.3$i 
done

/usr/local/bin/kubelet \
  --config=${CONFIGFILE} \
  --container-runtime=remote \
  --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \
  --image-pull-progress-deadline=2m \
  --kubeconfig=/var/lib/kubelet_certs/${HOSTNAME}.kubeconfig \
  --network-plugin=cni \
  --register-node=true \
  --logtostderr=false \
  --log-file=${LOGFILE} \
  --v=2
```

最後にkube-proxyです。
こいつはノードごとのネットワーク設定を、主にiptables（可変だけども）を用いて操作する担当です。
なのでServiceリソースを作った時なんかに各ノードにてiptablesでみてみると、確率でトラフィックを振り分けているServiceの実態が見えたりします。
それと、dockerコンテナの方に`--priviledged`をつけているにもかかわらずデフォルトで/proc/sys以下をReadOnlyモードでマウントするらしく、ここをReadWriteでリマウントしてあげないとこれもまたエラーになります。Workerノードは何もかも捧げているといった感じですね。
ここでもCIDRを指定していますが、ここでの意味合いは指定したCIDRレンジ以外へのトラフィックをマスカレードするという設定になるらしいです。

```bash
# in .
cat <<EOF | sudo tee lib/worker/kube-proxy/kube-proxy-config.yaml
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
clientConnection:
  kubeconfig: "/var/lib/kube-proxy/kube-proxy.kubeconfig"
mode: "iptables"
clusterCIDR: "10.200.0.0/16"
conntrack:
  maxPerCore: 0
EOF
```

```bash:bin/worker/exec/KUBE-PROXY_SERVICE.sh
CONFIGFILE="/var/lib/kube-proxy_config/kube-proxy-config.yaml"                                                                                                                                              
LOGFILE="/var/log/kube-proxy.log" 

#re-mount the Required File System if that was as ReadOnly mode
RFS='/proc/sys'
[[ $(awk -v RFS=${RFS} '/^proc/ {if($2==RFS){split($4, mode, ",")}}END{print mode[1]}' /proc/mounts) == 'ro' ]] && \
mount -o remount,rw,bind ${RFS} ${RFS}

/usr/local/bin/kube-proxy \
  --config=${CONFIGFILE} \
  --logtostderr=false \
  --log-file=${LOGFILE} \
  --v=2
```
以上で k8sクラスタコンポーネントの起動設定は完了です。
Workerも起動し、無事ノードとして認識されるか確かめて次のセクションに移動します。

```bash
# in .
$ docker-compose up -d worker-{0..2}

# 'container Ready'が表示されるくらいまで待ちます
$ docker exec -it ctl-0 kubectl get no
NAME       STATUS     ROLES    AGE   VERSION
worker-0   NotReady   <none>   7s    v1.18.1
worker-1   NotReady   <none>   7s    v1.18.1
worker-2   NotReady   <none>   7s    v1.18.1
```

## 10-configuring-kubectl
このセクションでは、自分作成したクラスタに対して任意の場所からアクセスできるようにします。
そのためにはkubeconfigという設定ファイルに接続先と認証情報を書き込み、kubectlを打つときにこの設定ファイルを参照するようにしてあげます。
まず接続先はbalancer（つまりdockerホストのネットワークデバイス（172.16.10.1）の10443ポート)になります。

```bash
# in .
$ (
  cd cert_util/

  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://172.16.10.1:10443

  kubectl config set-credentials admin \
    --client-certificate=admin.pem \
    --client-key=admin-key.pem \
    --embed-certs=true


  kubectl config set-context kubernetes-the-hard-way \
    --cluster=kubernetes-the-hard-way \
    --user=admin

  kubectl config use-context kubernetes-the-hard-way
)
```
kubectlが用いるconfigファイルは
1. `--kubeconfig`で指定した値
2. `$KUBECONFIG`の値
3. `${HOME}/.kube/config`

の順で参照されますので、特に何もいじっていないこの場合は`${HOME}/.kube/config`にここで設定した値が保存されています。

最後に疎通確認です。

```bash
# from docker host machine
$ kubectl get cs
NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok                  
controller-manager   Healthy   ok                  
etcd-2               Healthy   {"health":"true"}   
etcd-0               Healthy   {"health":"true"}   
etcd-1               Healthy   {"health":"true"} 
```

## 11-pod-network-routes
ここではあるpodから別のworkerノードで動いているpodに対しても疎通ができるようにルーティング設定を行います。具体的にはpodからのトラフィックを、宛先podが動いているworkerノードのethデバイスにルーティングするようにします。
本来GWであるdockerホストにおいて、これように作成されるデバイス用に下記のように設定を行うべきです、が、実は本チュートリアルにおいて、**この設定自体は`08-bootstrapping-kubernetes-controllers`及び`09-bootstrapping-kubernetes-workers`内のkubelet起動スクリプトの中に組み込んでいます**　ので、pod宛の通信は各ノードが直接ルーティングするようになっています。
これによりdockerコンテナの方をdownさせると自動的に設定自体も削除されるので、個人的にはよりクリーンかなと。
ということでこのセクションも割愛します。

一応解説だけしておくと、キモの部分は自分以外のpodへのトラフィックのみルーティングを行うという部分です。
今回workerノードとなっているdockerコンテナは、基本vethデバイスをdokcerホストマシン内に作成されるbridgeデバイスに差し込んで各種通信を行います。そのため各種通信は必ずホストマシンのbridgeデバイスを介して行われます。[詳しくは別で記事にしています。](https://qiita.com/DaichiSasak1/items/64732bb24826d38f8091)
同様のことがworkerとpodの間でも行われます。つまりpodからの通信はworker内に自動で生成されるbridgeデバイスを介して行われるので、もしここで自分の中で動いているpodへのトラフィックを自分のethデバイスにむけてルーティングするとそこでループとなりトラフィックが消失します（最初手探りで構築していたので、途中でトラフィックが消えていたことをコンテナのバグかと思い込みすごくハマりました、、、）。

## 12-dns-addon
ここではpod及びserviceリソースに対する名前解決を行えるようにするaddonをデプロイします。
kubernetesで使用されるDNSは、現在では[kube-dnsというのがレガシーでcorednsがモダン](https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#migration-to-coredns)のようです。
本家でも corednsを用いているので素直にその通りにします。
ただ当チュートリアルでは手作り感を出すためにserviceリソース用のCIDRを変更しており、本家で行っているデプロイ用のYAMLをそのまま用いることはできないので少しだけ変更を加えます。
まず、元となるYAMLをダウンロードします。

```bash
# in .
$ curl -O https://storage.googleapis.com/kubernetes-the-hard-way/coredns.yaml
# お好みのテキストエディタか下記コマンドで該当箇所修正します。
$ sed -i '/clusterIP/s/32/100/' coredns.yaml

$ kubectl apply -f coredns.yaml
serviceaccount/coredns created
clusterrole.rbac.authorization.k8s.io/system:coredns created
clusterrolebinding.rbac.authorization.k8s.io/system:coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created

# ちゃんと作成され、DNSサービスが機能するか確かめます
$ kubectl get pods -l k8s-app=kube-dns -n kube-system
NAME                       READY   STATUS    RESTARTS   AGE
coredns-589fff4ffc-d8745   1/1     Running   0          21s
coredns-589fff4ffc-k6zwl   1/1     Running   0          22s

$ kubectl run busy --image=busybox --rm -ti -- sh

# もしここでPermissionDeniedになったらkubeapi-to-kubeletのrbacが正常に作られているかcontrolプレーンの中で確認してみてください。
# もしくはとりあえず下記コマンド打ってみてください
# docker exec ctl-0 bash /exec/RBAC_AUTH.sh
#
# APIサーバを起動したときにデフォルトで作成されている'kubernetes'という名前のサービスリソースを名前解決します。
# in pod
$ nslookup kubernetes
Server:         10.100.0.10
Address:        10.100.0.10:53

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

$ exit
```
## 13-smoke-test
ここではkubernetesリソース関連の各種操作を行なっていきます。
ここに記載の範囲の操作は可能なことは確認できています。
#### secrets
ここでは`06-data-encryption-keys`で実施したsecretsリソースがちゃんと暗号化されていることを確認します。


```bash
# ctl-0にattach
$ docker exec -ti ctl-0 sh


$ kubectl create secret generic kubernetes-the-hard-way --from-literal="mykey=mydata"
$ etcdctl get \
  --cacert=/etc/etcd/ca.pem \
  --key=/etc/etcd/kubernetes-key.pem \
  --cert=/etc/etcd/kubernetes.pem \
  /registry/secrets/default/kubernetes-the-hard-way  | hexdump -C

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 84 82 28 36 9a 28 ef  |:v1:key1:..(6.(.|
#---<省略>---
```
aescbcという文字列が、このアルゴリズムがそれ以降文字列の暗号化に使われたということを示すようです。
試しにそれ以外のリソースについて**暗号化されていないのか**確かめてみます。

```bash
# still in ctl-0
# 'default' namespaceに関連して保存されているリソースの表示
$ etcdctl get \
  --cacert=/etc/etcd/ca.pem \
  --key=/etc/etcd/kubernetes-key.pem \
  --cert=/etc/etcd/kubernetes.pem  "" --prefix --keys-only | grep default

# kubernetesサービスリソースの情報確認
$ etcdctl get \
  --cacert=/etc/etcd/ca.pem \
  --key=/etc/etcd/kubernetes-key.pem \
  --cert=/etc/etcd/kubernetes.pem  /registry/services/specs/default/kubernetes | hexdump -C
```
outputs

```
00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 72 76 69 63  |/registry/servic|
00000010  65 73 2f 73 70 65 63 73  2f 64 65 66 61 75 6c 74  |es/specs/default|
00000020  2f 6b 75 62 65 72 6e 65  74 65 73 0a 6b 38 73 00  |/kubernetes.k8s.|
00000030  0a 0d 0a 02 76 31 12 07  53 65 72 76 69 63 65 12  |....v1..Service.|
00000040  87 04 0a bb 03 0a 0a 6b  75 62 65 72 6e 65 74 65  |.......kubernete|
00000050  73 12 00 1a 07 64 65 66  61 75 6c 74 22 00 2a 24  |s....default".*$|
00000060  35 39 66 35 32 61 62 62  2d 34 61 39 61 2d 34 63  |59f52abb-4a9a-4c|
00000070  30 34 2d 39 64 35 61 2d  35 33 36 30 32 34 32 34  |04-9d5a-53602424|
00000080  36 63 63 38 32 00 38 00  42 08 08 82 e7 9f f7 05  |6cc82.8.B.......|
00000090  10 00 5a 16 0a 09 63 6f  6d 70 6f 6e 65 6e 74 12  |..Z...component.|
000000a0  09 61 70 69 73 65 72 76  65 72 5a 16 0a 08 70 72  |.apiserverZ...pr|
000000b0  6f 76 69 64 65 72 12 0a  6b 75 62 65 72 6e 65 74  |ovider..kubernet|
000000c0  65 73 7a 00 8a 01 b8 02  0a 0e 6b 75 62 65 2d 61  |esz.......kube-a|
000000d0  70 69 73 65 72 76 65 72  12 06 55 70 64 61 74 65  |piserver..Update|
000000e0  1a 02 76 31 22 08 08 82  e7 9f f7 05 10 00 32 08  |..v1".........2.|
000000f0  46 69 65 6c 64 73 56 31  3a 85 02 0a 82 02 7b 22  |FieldsV1:.....{"|
00000100  66 3a 6d 65 74 61 64 61  74 61 22 3a 7b 22 66 3a  |f:metadata":{"f:|
00000110  6c 61 62 65 6c 73 22 3a  7b 22 2e 22 3a 7b 7d 2c  |labels":{".":{},|
00000120  22 66 3a 63 6f 6d 70 6f  6e 65 6e 74 22 3a 7b 7d  |"f:component":{}|
00000130  2c 22 66 3a 70 72 6f 76  69 64 65 72 22 3a 7b 7d  |,"f:provider":{}|
00000140  7d 7d 2c 22 66 3a 73 70  65 63 22 3a 7b 22 66 3a  |}},"f:spec":{"f:|
00000150  63 6c 75 73 74 65 72 49  50 22 3a 7b 7d 2c 22 66  |clusterIP":{},"f|
00000160  3a 70 6f 72 74 73 22 3a  7b 22 2e 22 3a 7b 7d 2c  |:ports":{".":{},|
00000170  22 6b 3a 7b 5c 22 70 6f  72 74 5c 22 3a 34 34 33  |"k:{\"port\":443|
00000180  2c 5c 22 70 72 6f 74 6f  63 6f 6c 5c 22 3a 5c 22  |,\"protocol\":\"|
00000190  54 43 50 5c 22 7d 22 3a  7b 22 2e 22 3a 7b 7d 2c  |TCP\"}":{".":{},|
000001a0  22 66 3a 6e 61 6d 65 22  3a 7b 7d 2c 22 66 3a 70  |"f:name":{},"f:p|
000001b0  6f 72 74 22 3a 7b 7d 2c  22 66 3a 70 72 6f 74 6f  |ort":{},"f:proto|
000001c0  63 6f 6c 22 3a 7b 7d 2c  22 66 3a 74 61 72 67 65  |col":{},"f:targe|
000001d0  74 50 6f 72 74 22 3a 7b  7d 7d 7d 2c 22 66 3a 73  |tPort":{}}},"f:s|
000001e0  65 73 73 69 6f 6e 41 66  66 69 6e 69 74 79 22 3a  |essionAffinity":|
000001f0  7b 7d 2c 22 66 3a 74 79  70 65 22 3a 7b 7d 7d 7d  |{},"f:type":{}}}|
00000200  12 43 0a 1a 0a 05 68 74  74 70 73 12 03 54 43 50  |.C....https..TCP|
00000210  18 bb 03 22 07 08 00 10  ab 32 1a 00 28 00 1a 0a  |...".....2..(...|
00000220  31 30 2e 31 30 30 2e 30  2e 31 22 09 43 6c 75 73  |10.100.0.1".Clus|
00000230  74 65 72 49 50 3a 04 4e  6f 6e 65 42 00 52 00 5a  |terIP:.NoneB.R.Z|
00000240  00 60 00 68 00 1a 02 0a  00 1a 00 22 00 0a        |.`.h......."..|
0000024e
"
```
情報が平文で保存されていることが確認できます。

#### deployment
ここではアプリケーションを運用していく上で主に使われることになるdeploymentリソースの作成です。

```bash
$ kubectl create deployment nginx --image=nginx
$ kubectl get po -w
# コンテナがRunningになるまで待ちます。確認できたらCtl-Cで抜けます。
```

#### Port Forwarding
ここではkubectlを用いるホスト（ここではdockerホスト）に、podのポートへとforwardingを行う操作の確認を行います。
主にデバッグ目的で行われる操作になります。

```bash
# 上のdeploymentで表示したpodの名前をコピーします。
$ POD_NAME=$(kubectl get pods -l app=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


# in new terminal of docker host
$ curl --head 127.0.0.1:8080
HTTP/1.1 200 OK
Server: nginx/1.19.0
Date: Mon, 15 Jun 2020 10:11:53 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 26 May 2020 15:00:20 GMT
Connection: keep-alive
ETag: "5ecd2f04-264"
Accept-Ranges: bytes
```

#### Logs
podのログを表示します。デバッグの基本となる、個人的には重要なコマンドです。
先ほどのアクセスログが表示されるはずです。

```bash
$ kubectl logs $POD_NAME
127.0.0.1 - - [15/Jun/2020:10:11:53 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.61.1" "-"
```
#### Exec
podの中でコマンドを実行します。
上記のログ同様、デバッグを行う上で結構用います。

```bash
kubectl exec -ti $POD_NAME -- nginx -v
```

#### Services
ここでは先ほど作成したpodに、その付加されたタグに該当すればトラフィックをフォワードするというサービスリソースを作成します。
kube-proxyを作成する段でも少し触れましたが、iptabesなどNetfilter操作系のコマンドで実装されているようです。
どのタグがどのpodについて、、とかを考えて作ると割と面倒なのですが、kubectlには一括でpodやdeploymentなどのリソースとサービスを紐づけるためのコマンドがあります。それが`expose`になります。
ここでは完全に外部からアクセスするため、NodePort型のサービスリソースを作成しています。
ClusterIPではあくまでpodへの通信をforwardするため、サービスクラスタのネットワークが見える環境でなければいけませんが（つまり今回の環境においてはホスト上で10.100.0.0/16への通信をどこかのノードへルーティングする必要があります）、NodePortはpodがデプロイされるworkerノードのポートからpodにフォワードするため、何もしなくでもdockerホストからpodへと疎通できるようになります。

```bash
$ kubectl expose deployment nginx --port 80 --type NodePort
$ kubectl get svc 
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE                                                                                                                                   
kubernetes   ClusterIP   10.100.0.1      <none>        443/TCP        162m                                                                                                                                  
nginx        NodePort    10.100.211.41   <none>        80:31867/TCP   7s 

$ NODE_PORT=$(kubectl get svc nginx \
  --output=jsonpath='{range .spec.ports[0]}{.nodePort}')
# 上で表示したportの値となる

# どこか適当なworkerノードのポートに疎通確認
$ curl -I 172.16.10.30:${NODE_PORT}
HTTP/1.1 200 OK
Server: nginx/1.19.0
Date: Mon, 15 Jun 2020 10:47:25 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 26 May 2020 15:00:20 GMT
Connection: keep-alive
ETag: "5ecd2f04-264"
Accept-Ranges: bytes
```

## 14-cleanup
ここまでに構築した環境を一掃します。
特にカスタマイズしてなければ下記コマンド一発です。

```bash
# in .
$ docker-compose down
```

ちなみにまたサービスをupすれば再度クリーンなkubernetesクラスタの実行環境が立ち上がります。

```bash
$ docker-compose up
```



# おわりに
以上、docker環境上でhard-wayするための方法と、つまづいたところの共有になります。

実際にやってみようとなっても、ここで行うテストの範囲ならかなり低リソース（再喝しますがdockerホストはCPU: １コア、RAM: 4GB、ディスク: 32GBです）でも十分動きました。全てフリーの範囲なのでお金の心配もありません。
意外とネットワーク周りの理解に手間取り結構時間かかってしまいましたが、コンテナ技術を理解するための入り口としてとてもいいチュートリアルでした。

hard-wayしたいけどパブリッククラウドに手を出すと後片付け忘れそうで不安、サインアップが面倒くさい、自前の環境で済ませたい、コンテナの制限を知りたいなど、自分と似た境遇の方の何かの助けになれば幸いです。