Edited at

Kubernetes on CentOS7


はじめに

数か月前より、EC2インスタンス上で稼働していた Web アプリケーションの一部を Google Kubernetes Engine(以降、GKE)に移行させています。

Kubernetes(以降、K8s)のマネージドサービスである GKE は、マスターノードやロードバランサ等、K8s を運用する基盤が既に整っている状態なので、K8s のアーキテクチャにそこまで精通していなくても使い始めることが出来ましたが、一から K8s 環境を構築してみたい欲求が湧いてきました。

そこで K8s のステージング環境が欲しかったこともあり(GKE でステージング環境を準備するとコストもかかりますし)、またオンプレミスに置いている VMware サーバのリソースにも余裕があったので、そこに K8s の環境を実装することにしました。

尚、K8s 界隈では Rancher を使ってゴニョゴニョする方法が ヒャッフー な感じですが、「もう少し理解を深めたい」という目的もあって、敢えて手作業で K8s 環境を実装しましたので、これから K8s に入門したい方の参考になれば幸いです。


ホスト構成

オンプレミスにある VMware(ESXi 6.0)2台の上に、4つの VM を立てます。

構成図-ホスト構成.png


クラスタ構成

各ノードの役割は次の通りです。


  • Master Node


    • API Server や Controller、Scheduler 等、マスターコンポーネントの Pod を配置



  • Worker Node #1 & #2


    • ユーザー側で用意したアプリケーション Pod を配置



  • Router Node


    • L4(MetalLB)、L7(Nginx Ingress)ロードバランサのコントローラ等を配置



構成図-クラスタ構成.png


VM の準備

VM 4台を準備し、K8s をインストールします。

(以降 kubelet を起動するところまで、4VM 全て共通の手順です)


VM の作成

VM を作成し、CentOS7 をインストールします。

※ VMware での VM作成手順、CentOS7 のインストール方法は省略します。


OS の初期設定

次のような CentOS7 の初期設定を行います。


  • Firewalld の停止、自動起動無効化

  • SELinux の無効化

  • ホスト名の設定 (& 内部向け DNS サーバに A レコードを登録)

  • NIC に固定IP割り当て

  • タイムゾーン設定(Asia/Tokyo≒JST)

  • OSを最新状態にアップデート(yum update

やり方はググればわんさか出てくるので、これも省略します。


swap 無効化

swap が有効になっていると kubelet の動作に影響するので無効化します。

$ sudo swapoff -a

更にリブートの際、swap が有効化されないよう /etc/fstab を編集します。


/etc/fstab

/dev/mapper/centos-swap swap                    swap    defaults        0 0

↓↓↓コメントアウト
#/dev/mapper/centos-swap swap swap defaults 0 0


Docker インストール

公式リポジトリから最新版の 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 docker-ce

$ sudo systemctl enable docker && sudo systemctl start docker


ルーティング設定

Pod 間通信が正しくルーティングされるよう、カーネルパラメータを追加します。

$ sudo sh -c "cat <<EOF >  /etc/sysctl.d/k8s.conf

net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
"

$ sudo sysctl --system


K8s リポジトリ準備

$ sudo sh -c "cat <<EOF > /etc/yum.repos.d/kubernetes.repo

[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
"


K8s インストール

$ sudo yum install -y kubelet kubeadm kubectl


kubelet 起動

kubelet はノードエージェントの役割を持っており、Pod のスケジューリングや管理を行うため、クラスタを構成する全ノードで起動させておく必要があります。

$ sudo systemctl enable kubelet && sudo systemctl start kubelet


Master ノードの作成


Master の初期化

Master を kubeadm init コマンドで初期化します。

尚、パラメータの意味は次の通りです。


  • --apiserver-advertise-address: API Server のListen IPアドレス、通常は Master ノードの NIC eth0 の IP アドレスを指定

  • --pod-network-cidr: Pod に割り当てる内部 IP アドレスの CIDR、後述の Flannel を CNI に採用する場合は 10.244.0.0/16 を指定

  • --service-cidr: Service に割り当てる内部 IP アドレスの CIDR、デフォルトは 10.96.0.0/12

$ sudo kubeadm init \

--apiserver-advertise-address 172.16.64.48 \
--pod-network-cidr 10.244.0.0/16 \
--service-cidr 172.16.130.0/24
[init] using Kubernetes version: v1.12.1
[preflight] running pre-flight checks
[preflight/images] Pulling images required for setting up a Kubernetes cluster

・・・(省略)

Your Kubernetes master 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 machines by running the following on each node
as root:

kubeadm join 172.16.64.48:6443 --token 9deqgn.ub69e9rt8orxdjuz --discovery-token-ca-cert-hash sha256:4cd706f26bfbff6490c771f345c5c2a897c31d264eb430b2b1b4797d383b8a4e

Your Kubernetes master has initialized successfully! が表示されていれば初期化完了です。

またメッセージの最終行 kubeadm join ... は、Node を クラスタへ参加させる際に利用するコマンドなので控えておきます。


kubectl 接続設定

kubectl コマンドでクラスタを操作するには、鍵による接続設定が必要です。

それらの設定は Master 初期化の際に自動で生成してくれているので、カレントユーザーがアクセスできる場所にコピーします。

$ mkdir -p $HOME/.kube

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


CNI プラグインのインストール

Master 初期化直後の Node と Pod の状態を確認すると、Pod にある corednsPending の状態から変わらず、何時まで待っても起動してくれません。

$ kubectl get node

NAME STATUS ROLES AGE VERSION
master.k8s.holdings.local Ready master 6s v1.12.1

$ kubectl get pod --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-576cbf47c7-lxsvc 0/1 Pending 0 2m9s
kube-system coredns-576cbf47c7-vpl2s 0/1 Pending 0 2m9s
kube-system etcd-master.k8s.holdings.local 1/1 Running 0 78s
kube-system kube-apiserver-master.k8s.holdings.local 1/1 Running 0 98s
kube-system kube-controller-manager-master.k8s.holdings.local 1/1 Running 0 93s
kube-system kube-proxy-g47f2 1/1 Running 0 2m9s
kube-system kube-scheduler-master.k8s.holdings.local 1/1 Running 0 80s

これはクラスタ上に Container Network Interface(CNI)プラグインがデプロイされていないためです。

そこで CNI プラグインのひとつである Flannel を、次のコマンドでデプロイします。

$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

再度 Pod の状態を確認すると kube-flannel がデプロイされ、coredns も起動されていることが判ります。

$ kubectl get pod --all-namespaces

NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-576cbf47c7-lxsvc 1/1 Running 0 3m18s
kube-system coredns-576cbf47c7-vpl2s 1/1 Running 0 3m18s
kube-system etcd-master.k8s.holdings.local 1/1 Running 0 2m27s
kube-system kube-apiserver-master.k8s.holdings.local 1/1 Running 0 2m47s
kube-system kube-controller-manager-master.k8s.holdings.local 1/1 Running 0 2m42s
kube-system kube-flannel-ds-amd64-5dn58 1/1 Running 0 15s
kube-system kube-proxy-g47f2 1/1 Running 0 3m18s
kube-system kube-scheduler-master.k8s.holdings.local 1/1 Running 0 2m29s

以上で Master Node の準備は完了です。


Woker(#1 & #2)、Router Node の作成


IP転送の有効化

IP 転送(IP Forwarding)とは、ホスト自体に設定されている IP アドレス以外を宛先とする IP パケットを受信した場合、その宛先に IP パケットを転送する機能です。(ホスト自体がルーターとなり、パケットを中継するイメージ)

CentOS7 のデフォルトでは IPv4 Forwarding 機能が無効化されているので、次のコマンドでカーネルパラメータを追加して有効化します。

$ sudo sh -c "echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/k8s.conf"

$ sudo sysctl --system


クラスタに参加

前述「Master の初期化」で控えておいた kubeadm join コマンドを、Master 以外の各ノードで実行してクラスタに追加します。

$ sudo kubeadm join 172.16.64.48:6443 \

--token 9deqgn.ub69e9rt8orxdjuz \
--discovery-token-ca-cert-hash sha256:4cd706f26bfbff6490c771f345c5c2a897c31d264eb430b2b1b4797d383b8a4e

Master Node 側のコンソールから、kubectl コマンドで Node と Pod の状態を確認します。

$ kubectl get node

NAME STATUS ROLES AGE VERSION
master.k8s.holdings.local Ready master 6m v1.12.1
router.k8s.holdings.local Ready <none> 6m v1.12.1
worker1.k8s.holdings.local Ready <none> 6m v1.12.1
worker2.k8s.holdings.local Ready <none> 6m v1.12.1

$ kubectl get pod --all-namespaces -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
kube-system coredns-576cbf47c7-lxsvc 1/1 Running 0 18m 10.244.0.7 master.k8s.holdings.local <none>
kube-system coredns-576cbf47c7-vpl2s 1/1 Running 0 18m 10.244.0.6 master.k8s.holdings.local <none>
kube-system etcd-master.k8s.holdings.local 1/1 Running 0 17m 172.16.64.48 master.k8s.holdings.local <none>
kube-system kube-apiserver-master.k8s.holdings.local 1/1 Running 0 17m 172.16.64.48 master.k8s.holdings.local <none>
kube-system kube-controller-manager-master.k8s.holdings.local 1/1 Running 0 17m 172.16.64.48 master.k8s.holdings.local <none>
kube-system kube-flannel-ds-amd64-5dn58 1/1 Running 0 15m 172.16.64.48 master.k8s.holdings.local <none>
kube-system kube-flannel-ds-amd64-cg6m7 1/1 Running 0 39s 172.16.64.49 worker1.k8s.holdings.local <none>
kube-system kube-flannel-ds-amd64-gv8jd 1/1 Running 0 11s 172.16.64.50 worker2.k8s.holdings.local <none>
kube-system kube-flannel-ds-amd64-pztzg 1/1 Running 0 3m57s 172.16.64.51 router.k8s.holdings.local <none>
kube-system kube-proxy-8fqbs 1/1 Running 0 39s 172.16.64.49 worker1.k8s.holdings.local <none>
kube-system kube-proxy-9lwp5 1/1 Running 0 3m57s 172.16.64.51 router.k8s.holdings.local <none>
kube-system kube-proxy-g47f2 1/1 Running 0 18m 172.16.64.48 master.k8s.holdings.local <none>
kube-system kube-proxy-nbr9w 1/1 Running 0 11s 172.16.64.50 worker2.k8s.holdings.local <none>
kube-system kube-scheduler-master.k8s.holdings.local 1/1 Running 0 17m 172.16.64.48 master.k8s.holdings.local <none>

Node が4つで構成され、全ての Pod が正常に起動(STATUS=Running)されていれば完了です。


metrics-server

クラスタのリソース(CPU/メモリ/ストレージ)利用状況を確認するコマンドに kubectl top があります。

このコマンドですが、クラスタ上で metrics-server が稼働していない状況だと、次のようなエラーメッセージが表示され利用することができません。

$ kubectl top nodes

Error from server (NotFound): the server could not find the requested resource (get services http:heapster:)

そのため、次の手順で metrics-server をデプロイします。

GitHub リポジトリからプロジェクトをダウンロードします。

$ git clone https://github.com/kubernetes-incubator/metrics-server

マニフェストファイル metrics-server/deploy/1.8+/metrics-server-deployment.yaml を編集します。


metrics-server/deploy/1.8+/metrics-server-deployment.yaml

---

apiVersion: v1
kind: ServiceAccount
metadata:
name: metrics-server
namespace: kube-system
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: metrics-server
namespace: kube-system
labels:
k8s-app: metrics-server
spec:
selector:
matchLabels:
k8s-app: metrics-server
template:
metadata:
name: metrics-server
labels:
k8s-app: metrics-server
spec:
serviceAccountName: metrics-server
volumes:
# mount in tmp so we can safely use from-scratch images and/or read-only containers
- name: tmp-dir
emptyDir: {}
containers:
- name: metrics-server
image: k8s.gcr.io/metrics-server-amd64:v0.3.1
###追加 - ここから
command:
- /metrics-server
- --kubelet-insecure-tls
###追加 - ここまで
imagePullPolicy: Always
volumeMounts:
- name: tmp-dir
mountPath: /tmp
###追加 - ここから
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
nodeSelector:
node-role.kubernetes.io/master: ""
###追加 - ここまで

###追加 - ここから から ###追加 - ここまで の間を、所定の場所に追記すればOKです。

追加内容を軽く説明すると次のようになります。

spec.template.spec.containers[].command の部分は metrics-serverkubelet と通信する際、kubelet から提示された CA 証明書を検証しないようにする設定です(追加していないと証明書関連のエラーが発生し、metrics-server が動作しません)。今回はテスト環境なのでこのような設定で問題ないですが、本番環境ではこの辺りはキチンと設定した方が良さそうです。

spec.template.spec.tolerations[]spec.template.spec.nodeSelector の部分は、metrics-server を Master Node にデプロイさせるようにするおまじないです。(詳しくは後述します。)

マニフェストファイルの編集が済んだら、metrics-server をデプロイします。

$ kubectl apply -f metrics-server/deploy/1.8+/

clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
serviceaccount/metrics-server created
deployment.extensions/metrics-server created
service/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created

Master Node に metrics-server がデプロイされたか Pod 情報を確認します。

$ kubectl get pod -n kube-system -o wide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
・・・
metrics-server-787c588977-lrfhv 1/1 Running 0 5m40s 10.244.0.9 master.k8s.holdings.local <none>
・・・

これでクラスタのリソース情報を kubectl top コマンドで確認できるようになります。

$ kubectl top nodes

NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
master.k8s.holdings.local 177m 8% 1111Mi 63%
router.k8s.holdings.local 44m 2% 308Mi 17%
worker1.k8s.holdings.local 43m 2% 245Mi 14%
worker2.k8s.holdings.local 82m 4% 284Mi 16%

$ kubectl top pod --all-namespaces
NAMESPACE NAME CPU(cores) MEMORY(bytes)
kube-system coredns-576cbf47c7-lxsvc 2m 21Mi
kube-system coredns-576cbf47c7-vpl2s 3m 16Mi
kube-system etcd-master.k8s.holdings.local 21m 58Mi
kube-system kube-apiserver-master.k8s.holdings.local 33m 442Mi
kube-system kube-controller-manager-master.k8s.holdings.local 33m 76Mi
kube-system kube-flannel-ds-amd64-5dn58 3m 20Mi
kube-system kube-flannel-ds-amd64-cg6m7 3m 15Mi
kube-system kube-flannel-ds-amd64-gv8jd 4m 11Mi
kube-system kube-flannel-ds-amd64-pztzg 4m 13Mi
kube-system kube-proxy-8fqbs 5m 11Mi
kube-system kube-proxy-9lwp5 3m 11Mi
kube-system kube-proxy-g47f2 2m 18Mi
kube-system kube-proxy-nbr9w 3m 11Mi
kube-system kube-scheduler-master.k8s.holdings.local 12m 22Mi
kube-system metrics-server-787c588977-lrfhv 2m 13Mi


Master 以外のノードに Role 設定

ノード情報を確認すると Master Node のみ ROLESmaster が表示され、その他のノードは <none> と表示され、何も設定されていないことが判ります。

$ kubectl get node

NAME STATUS ROLES AGE VERSION
master.k8s.holdings.local Ready master 6d2h v1.12.1
router.k8s.holdings.local Ready <none> 6d2h v1.12.1
worker1.k8s.holdings.local Ready <none> 6d2h v1.12.1
worker2.k8s.holdings.local Ready <none> 6d2h v1.12.1

この ROLES ですが、どの情報から表示されているか調べてみると、ノードの Roles に設定されている内容が表示されていました。(kubectl describe node で確認可能)

このまま <none> の状態で運用しても支障はありませんが、ノード情報を確認するだけで各ノードの役割が解りやすくなるよう Roles を設定します。

$ kubectl label node worker1.k8s.holdings.local node-role.kubernetes.io/worker=

$ kubectl label node worker2.k8s.holdings.local node-role.kubernetes.io/worker=
$ kubectl label node router.k8s.holdings.local node-role.kubernetes.io/router=

ちなみに本投稿時点では、この Roles は K8s では利用されておらず、何を設定しても特に問題は発生しないそうです。(将来、特別な意味を持って使われるようになる可能性はありますが)

各ノードに Role が設定されたか確認します。

$ kubectl get node

NAME STATUS ROLES AGE VERSION
master.k8s.holdings.local Ready master 6d2h v1.12.1
router.k8s.holdings.local Ready router 6d2h v1.12.1
worker1.k8s.holdings.local Ready worker 6d2h v1.12.1
worker2.k8s.holdings.local Ready worker 6d2h v1.12.1


Load Balancer(LB)コントローラの準備

GKE 等のパブリッククラウドで提供される K8s サービスでは、ベンダーがマネージド サービスを提供してくれているので、ユーザーは Service や Ingress を定義するだけで Load Balancing を実装できますが、オンプレミスの K8s ではユーザー自身で LB コントローラを実装する必要があります。

そこで今回は Router Node 上へ Layer-4(L4)の LB コントローラに MetalLB、Layer-7(L7)の LB コントローラに Nginx Ingress Controller をデプロイします。


Router Node に Taints 設定

前述 metrics-server の項で「おまじない」と説明しましたが、K8s には 特定の Pod を 特定の Node に配置させる(またはさせない)方法について、次のメカニズムが用意されています。

今回はこれらメカニズムの内、最も柔軟な条件設定ができる Taints & Tolerations を利用し、Router Node に Taints を設定して、LB コントローラ以外はスケジュールされないようにします。

(Taints & Tolerations については、こちらで詳しく説明されていました)

ちなみに Master Node には Master 初期化の際に Taints が設定されており、Torerations を指定しない限り Pod がスケジューリングされないようになっています。

$ kubectl describe node master.k8s.holdings.local | grep Taints

Taints: node-role.kubernetes.io/master:NoSchedule

Router Node に Taints を設定します。

$ kubectl taint nodes router.k8s.holdings.local node-role.kubernetes.io/router=:NoSchedule

Taints が正しく設定されたか確認します。

$ kubectl describe node router.k8s.holdings.local | grep Taints

Taints: node-role.kubernetes.io/router:NoSchedule


MetalLB のデプロイ

MetalLB は Google 社が作った ベアメタル K8s クラスタ向けのロードバランサ実装です。

この MetalLB をクラスタにデプロイすることで、オンプレミス K8s 環境でも L4 ロードバランサ(≒ type: LoadBalancer)を利用することができます。

最初に MetalLB のマニフェストをダウンロードします。

$ wget https://raw.githubusercontent.com/google/metallb/master/manifests/metallb.yaml

speakercontroller が Router Node にデプロイされるよう、マニフェストファイル metallb.yaml を編集します。

speaker は DaemonSet なので、編集しなければ Worker Node だけにデプロイされ、Router Node にデプロイされない。

Controller は Deployment なので、編集しなければ Worker Node のどちらかにデプロイされてしまう。


metallb.yaml

(省略)

---
apiVersion: apps/v1beta2
kind: DaemonSet
metadata:
namespace: metallb-system
name: speaker
labels:
app: metallb
component: speaker
spec:
selector:
matchLabels:
app: metallb
component: speaker
template:
metadata:
labels:
app: metallb
component: speaker
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "7472"
spec:
serviceAccountName: speaker
terminationGracePeriodSeconds: 0
hostNetwork: true
containers:
- name: speaker
image: metallb/speaker:master
imagePullPolicy: Always
args:
- --port=7472
- --config=config
env:
- name: METALLB_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
ports:
- name: monitoring
containerPort: 7472
resources:
limits:
cpu: 100m
memory: 100Mi

securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- all
add:
- net_raw
###追加 - ここから
tolerations:
- key: "node-role.kubernetes.io/router"
operator: "Equal"
effect: "NoSchedule"
###追加 - ここまで
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
namespace: metallb-system
name: controller
labels:
app: metallb
component: controller
spec:
revisionHistoryLimit: 3
selector:
matchLabels:
app: metallb
component: controller
template:
metadata:
labels:
app: metallb
component: controller
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "7472"
spec:
serviceAccountName: controller
terminationGracePeriodSeconds: 0
securityContext:
runAsNonRoot: true
runAsUser: 65534 # nobody
containers:
- name: controller
image: metallb/controller:master
imagePullPolicy: Always
args:
- --port=7472
- --config=config
ports:
- name: monitoring
containerPort: 7472
resources:
limits:
cpu: 100m
memory: 100Mi

securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- all
readOnlyRootFilesystem: true
###追加 - ここから
tolerations:
- key: "node-role.kubernetes.io/router"
operator: "Equal"
effect: "NoSchedule"
###追加 - ここまで
---


編集したマニフェストファイルを元に、MetalLB をデプロイします。

$ kubectl apply -f metallb.yaml

Pod 情報を確認し、Master 以外のノードに speakerが、Router Node に controller がデプロイされていればOKです。

$ kubectl get pod -n metallb-system -o wide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
controller-5cc7776b6f-mxchn 1/1 Running 0 8m1s 10.244.1.4 router.k8s.holdings.local <none>
speaker-5c5hz 1/1 Running 0 8m38s 172.16.64.51 router.k8s.holdings.local <none>
speaker-fd5kq 1/1 Running 0 8m38s 172.16.64.49 worker1.k8s.holdings.local <none>
speaker-zt9b9 1/1 Running 0 8m38s 172.16.64.50 worker2.k8s.holdings.local <none>

MetalLB の動作モードには、BGP モードL2 モード があります。

BGP モードはクラスタネットワークの上流に BGP 対応ネットワーク機器が必要なので、今回は L2 モードで MetalLB が動作するよう設定したマニフェストファイル metallb-l2.yaml を準備します。

マニフェスト内の addresses には、Service に割り当てる External IP のアドレス範囲を指定してください。

172.16.131.0/24 と指定すると、172.16.131.0 を External IP に割り当ててしまう可能性有り)


metallb-l2.yaml

apiVersion: v1

kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 172.16.131.1-172.16.131.254

マニフェストファイルを元に、MetalLB の動作モードを設定します。

$ kubectl apply -f metallb-l2.yaml


Nginx Ingress Controller のデプロイ

Nginx Ingress ControllerKubernetes.io が提供する Nginx ベースの Ingress(L7)コントローラです。

この Ingress コントローラ を Router Node 上に配置して、L7 ロードバランサ を実装します。

最初にコントローラ周りを設定するマニフェストファイル mandatory.yaml をダウンロードします。

$ wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml

コントローラが Router Node にデプロイされるよう、tolerations を追加します。


mandatory.yaml

(省略)

---

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
serviceAccountName: nginx-ingress-serviceaccount
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 33
runAsUser: 33
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
###追加 - ここから
tolerations:
- key: "node-role.kubernetes.io/router"
operator: "Equal"
effect: "NoSchedule"
###追加 - ここまで
---


編集したマニフェストファイルを元に、Ingress Controller をデプロイします。

$ kubectl apply -f mandatory.yaml

Nginx Ingress Controller のエンドポイントとなるサービス ingress-nginx を作成します。

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/cloud-generic.yaml

Pod と Service 情報を確認します。

nginx-ingress-controller が Router Node にデプロイされ、ingress-nginx Service が作成されていれば OK です。

[centos@master ~]$ kubectl get pod,svc -n ingress-nginx -o wide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
pod/nginx-ingress-controller-844d47b8b6-4zhrf 1/1 Running 0 4d5h 10.244.1.8 router.k8s.holdings.local <none>

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/ingress-nginx LoadBalancer 172.16.130.122 172.16.131.1 80:30776/TCP,443:32665/TCP 4d5h app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx

ingress-nginx Service に割当てられた IP アドレス(EXTERNAL-IP)にブラウザからアクセスして 404 Not Found と表示されれば正常に動作しています。

FireShot Capture 20 - Welcome to nginx! - http___172.16.131.2_.png


K8s DashBoard のデプロイ

今回のクラスタはオンプレミスにあるテスト環境なので、認証せずにアクセスできる DashBoard を用意します。

最初に K8s DashBoard のマニフェストファイルをダウンロードします。

$ wget https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/alternative/kubernetes-dashboard.yaml

ダウンロードしたマニフェストファイル kubernetes-dashboard.yaml を編集し、Deployment に nodeSelector を追加(tolerations 設定だけだと Worker Node にデプロイされることがある為)、Service に type: NodePort を追加します。


kubernetes-dashboard.yaml

(省略)

---
# ------------------- Dashboard Deployment ------------------- #

kind: Deployment
apiVersion: apps/v1beta2
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: kubernetes-dashboard
template:
metadata:
labels:
k8s-app: kubernetes-dashboard
spec:
containers:
- name: kubernetes-dashboard
image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.0
ports:
- containerPort: 9090
protocol: TCP
args:
# Uncomment the following line to manually specify Kubernetes API server Host
# If not specified, Dashboard will attempt to auto discover the API server and connect
# to it. Uncomment only if the default does not work.
# - --apiserver-host=http://my-address:port
volumeMounts:
# Create on-disk volume to store exec logs
- mountPath: /tmp
name: tmp-volume
livenessProbe:
httpGet:
path: /
port: 9090
initialDelaySeconds: 30
timeoutSeconds: 30
volumes:
- name: tmp-volume
emptyDir: {}
serviceAccountName: kubernetes-dashboard
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
###追加 - ここから
nodeSelector:
node-role.kubernetes.io/master: ""
###追加 - ここまで
---
# ------------------- Dashboard Service ------------------- #

kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
spec:
###追加 - ここから
type: NodePort
###追加 - ここまで
ports:
- port: 80
targetPort: 9090
selector:
k8s-app: kubernetes-dashboard


DashBoard Service Account にクラスタ管理者権限を付与します。

$ cat <<EOF | kubectl create -f -

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard
labels:
k8s-app: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kube-system
EOF

最後に DashBoard をデプロイします。

$ kubectl apply -f kubernetes-dashboard.yaml

Pod と Service 情報を参照し、Master Node に kubernetes-dashboard がデプロイされ、Service kubernetes-dashboard が作成されていることを確認します。

$ kubectl get pod,svc -n kube-system -o wide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
・・・
kubernetes-dashboard-6c4676f6db-65sdq 1/1 Running 0 58s 10.244.0.10 master.k8s.holdings.local <none>
・・・

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
・・・
service/kubernetes-dashboard NodePort 172.16.130.215 <none> 80:30246/TCP 8m33s k8s-app=kubernetes-dashboard
・・・

ブラウザから http://master.k8s.holdings.local:30246 にアクセスし、DashBoard が表示されれば完了です。

※ポート 30246kubernetes-dashboard Service の Node Port (上記)

FireShot Capture 19 - 概要 - Kubernetes Dashboar_ - http___master.k8s.holdings.local_30246_#!_overview.png


テストしてみる

K8s テスト環境上で、実際にWebアプリケーションをデプロイして動作を確認します。

テスト用の Web アプリケーションとして Nginx の Pod をデプロイします。

$ cat <<EOF | kubectl apply -f -

apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1
ports:
- name: http
containerPort: 80
EOF

nginx の Pod が Worker Node にデプロイされているか確認します。

$ kubectl get pod -o wide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx-cdd8d77b-szbdf 1/1 Running 0 23s 10.244.2.13 worker1.k8s.holdings.local <none>

Service を作成します。type: LoadBalancer と設定しているので、作成された Service は L4 ロードバランサとして動作します。

$ cat <<EOF | kubectl apply -f -

apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
EOF

作成された Service の EXTERNAL-IP を確認します。

$ kubectl get svc

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 172.16.130.252 172.16.131.2 80:30948/TCP 14s

ブラウザから EXTERNAL-IP にアクセス (http://172.16.131.2/ )して Nginx のデフォルトページが表示されれば正常に動作しています。

FireShot Capture 20 - Welcome to nginx! - http___172.16.131.2_.png

Ingress を作成します。作成された Ingress は L7 ロードバランサとして動作します。

$ cat <<EOF | kubectl apply -f -

apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: nginx.holdings.local
http:
paths:
- path: /
backend:
serviceName: nginx
servicePort: 80
EOF

作成された Service(実際には前述で作成された Service の typeNodePort に変更されただけ)、Ingress を確認します。

$ kubectl get svc,ing

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx NodePort 172.16.130.252 <none> 80:30948/TCP 12m

NAME HOSTS ADDRESS PORTS AGE
ingress.extensions/nginx nginx.holdings.local 80 10s

よく見ると Ingress の ADDRESS が空欄になっています。

GKE では明示的に指定された IP アドレス(プライベート or パブリック)が割り当てられ、そのアドレス が Ingress の Listen IP になりますが、Nginx Ingress では Controller をデプロイする際に作成した Service ingress-nginxEXTERNAL-IP(今回の場合、172.16.131.1) が Listen IP となります。

次に Ingress のマニフェストで指定したホスト名(nginx.holdings.local)を ingress-nginx の Listern IP(172.16.131.1)で名前解決できるよう、内部 DNS サーバに登録します。(DNSへの登録方法は省略します)

DNS サーバへの登録が終わったら、ブラウザから http://nginx.holdings.local にアクセスしてみてください。

Nginx のデフォルトページが表示されれば正常に動作しています。

FireShot Capture 20 - Welcome to nginx! - http___172.16.131.2_.png

以上で K8s テスト環境の構築は完了です。


さいごに


  • 今回は永続ディスクについて触れませんでしたが、必要に応じ NFS サーバ用の VM を立てる等の方法で永続ディスクを実装できます。

  • Master Node や Router Node ですが、今回はステージング環境なためシングルノードで構成しました。もし本番環境として実装するのであれば、それらのノードが SPOF にならないよう、冗長な構成をとる必要があると思います。

  • 本投稿がお役に立てたようでしたら、そっと「いいね」をお願いします。

  • 次は Knative やりたい。