Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

Kubernets 클러스터 구성하기

Kubernets 클러스터를 구성하려고 한다. Minikube라고 로컬에 Kubernetes를 설치할 수 있는 애플리케이션이 있는 것 같기는 한데, 학습이나 테스트 용도 정도로 사용하는 것 같다. 내가 원하는 건 실제 환경에서 바로 써먹을 수 있는 기술을 익히는 거다. 그래서 VirtualBox 기반으로 클러스터를 구성하기로 했다.

클러스터 구성 환경

구성 환경은 아래와 같다.
  • 호스트 PC : AMD Ryzen 7 2700 8core 16thread, 16G Memory
  • 호스트 운영체제 : 우분투리눅스 18.04
  • VirtualBox 버전 : 5.2.10
  • 게스트 운영체제 : 우분투리눅스 17.10
하나의 Kubernetes Master와 3개의 Kubernets Node로 구성하기로 했다. Kubernetes의 구성요소는 Kubernetes overview 문서를 참고하자.

 kubernetes virtualbox 환경

VirtualBox 테스트 환경을 위해서 vboxnet0네트워크를 만들었다. 이 네트워크는 192.168.56.0/24 서브넷을 가지며 게이트웨이는 192.168.56.1 이다. 인터넷으로 나갈 수 있어야 하므로 masquerade 구성을 했다.
# echo 1 > /proc/sys/net/ipv4/ip_forward
# iptables -t nat -A POSTROUTING -o enp3s0 -j MASQUERADE
# iptables -A FORWARD -o enp3s0 -j ACCEPT
# iptables -A FORWARD -i enp3s0 -j ACCEPT
  1. kubemaster : kubernetes Master 노드. IP주소는 192.168.56.101 이다.
  2. kubenode-01 ~ kubenode-03 : Kubernets 노드다. 192.168.56.102에서 192.168.56.104 까지의 IP를 가지고 있다.

docker 설치

도커 설치문서를 참고하자. 설치한 도커 버전은 아래와 같다.
# docker version
Client:
 Version:           18.06.0-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        0ffa825
 Built:             Wed Jul 18 19:09:56 2018
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.0-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       0ffa825
  Built:            Wed Jul 18 19:07:55 2018
  OS/Arch:          linux/amd64
  Experimental:     false
kubemaster과 모든 kubenode에 동일한 버전을 설치했다.

kubeadm

kubeadm은 kubernetes 클러스터의 설정에 도움을 주는 툴이다. Kubernetes 1.4.0 버전부터 배포본의 일부로 제공된다. Kubernetes의 단점 중 하나는 "설치가 어렵다"는 것이였다. Kubeadm을 이용하면 보다 쉽게 설치 할 수 있다.
apt-get update && apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl

kubeadmin init명령을 실행하면, 현재 노드를 kubernetes master로 설정할 수 있다. 그 전에 kubenetes 네트워크를 먼저 선택하기로 했다. Kubenetes는 CNI를 이용해서 다양한 종류의 컨테이너 네트워크를 애드온 형식으로 올릴 수 있다. Calico, Canal, Flannel, Kube-router, Romana, Weave Net 등의 컨테이너 네트워크를 지원하는데, 나는 Kube-router를 설치하기로 했다. 왜냐면 "가장 쉬울 것 같아서"다.

Kube-router를 설치하기 위해서는 /proc/sys/net/bridge/bridge-nf-call-iptables 값을 1로 해야 한다.
# sysctl net.bridge.bridge-nf-call-iptables=1

kubeadmin init를 마스터노드를 설정했다. 이때 kube-router를 설정하기 위해서--pod-network-cidr 플래그도 함께 실행했다.
# kubeadmin init --pod-network-cidr 10.100.0.0/16
[init] using Kubernetes version: v1.11.2
[preflight] running pre-flight checks
I0814 20:14:03.322499   20387 kernel_validator.go:81] Validating kernel version
I0814 20:14:03.322796   20387 kernel_validator.go:96] Validating kernel config

...... // 생략
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 192.168.56.2:6443 --token p0qlr7.zov9st9zfq7c07va --discovery-token-ca-cert-hash sha256:ced8b2f88b5e4027ef0e2d96565e607eed5f08c6b830be5861c6d32287ce5c6e
성공적으로 실행하고 나면, 클러스터를 이용하기 위한 가이드가 나온다. 먼저 kubernetes를 위한 설정파일을 복사한다.
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl로 노드 상태를 확인해보자.
# kubectl get nodes
NAME         STATUS     ROLES     AGE       VERSION
kubemaster   NotReady   master    4m        v1.11.2
아직 kubemaster가 NotReady 상태다. pod 상태를 마저 확인했다.
# kubectl get pods --all-namespaces 
NAMESPACE     NAME                                 READY     STATUS    RESTARTS   AGE
kube-system   coredns-78fcdf6894-cmw4z             0/1       Pending   0          4m
kube-system   coredns-78fcdf6894-stgvv             0/1       Pending   0          4m
kube-system   etcd-kubemaster                      1/1       Running   0          3m
kube-system   kube-apiserver-kubemaster            1/1       Running   0          4m
kube-system   kube-controller-manager-kubemaster   1/1       Running   0          4m
kube-system   kube-proxy-72lhm                     1/1       Running   0          4m
kube-system   kube-scheduler-kubemaster            1/1       Running   0          4m
coredns가 Pending 상태다. 아직 kube-router가 준비가 안된상태라서 그렇다. kube-router 설정을 마무리하자.
KUBECONFIG=/etc/kubernetes/admin.conf kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/master/daemonset/kubeadm-kuberouter.yaml
KUBECONFIG=/etc/kubernetes/admin.conf kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/master/daemonset/kubeadm-kuberouter-all-features.yaml
다시 kubemaster 상태를 확인해 보자.
# kubectl get nodes
NAME         STATUS    ROLES     AGE       VERSION
kubemaster   Ready     master    10m       v1.11.2

# kubectl get pods --all-namespaces 
NAMESPACE     NAME                                 READY     STATUS    RESTARTS   AGE
kube-system   coredns-78fcdf6894-cmw4z             1/1       Running   0          8m
kube-system   coredns-78fcdf6894-stgvv             1/1       Running   0          8m
kube-system   etcd-kubemaster                      1/1       Running   0          1m
kube-system   kube-apiserver-kubemaster            1/1       Running   0          1m
kube-system   kube-controller-manager-kubemaster   1/1       Running   0          1m
kube-system   kube-proxy-72lhm                     1/1       Running   0          8m
kube-system   kube-router-jl5cm                    1/1       Running   0          1m
kube-system   kube-scheduler-kubemaster            1/1       Running   0          1m

kubemaster가 Ready 상태가 되고, kube-system 네임스페이스의 프로세스들도 정상적으로 올라왔다. 이제 kubenode들을 클러스터에 join 하면 된다. 3개의 kubenode 에서 아래 명령을 모두 실행하면 된다.
kubeadm join 192.168.56.2:6443 --token p0qlr7.zov9st9zfq7c07va --discovery-token-ca-cert-hash sha256:ced8b2f88b5e4027ef0e2d96565e607eed5f08c6b830be5861c6d32287ce5c6e

kubenode들이 클러스터에 join 했는지 확인해보자.
# kubectl get nodes
NAME          STATUS    ROLES     AGE       VERSION
kubemaster    Ready     master    45m       v1.11.2
kubenode-01   Ready     <none>    1m        v1.11.2
kubenode-02   Ready     <none>    18s       v1.11.2
kubenode-03   Ready     <none>    12s       v1.11.2

hello world service 실행

클러스터에 hello world 애플리케이션을 배포해보기로 했다.
kubectl run hello-world --replicas=2 --labels="run=load-balancer-example" --image=gcr.io/google-samples/node-hello:1.0  --port=8080
kubernetes의 최소 배포단위는 Pod다. Pod는 컨테이너의 그룹으로 예를 들어 wordpress Pod라면 Apache+PHP 컨테이너와 Mysql 컨테이너 로 구성이 될 것이다. 이 Pod에 있는 컨테이너들은 하나의 물리서버에 위치한다. kubectl을 이용해서 이런 Pod를 만들 수 있다. Pod가 하나의 물리서버에만 위치하므로 안전하게 구성하기 위해서는 2개 이상의 Pod를 만들어서 배포히야 한다. --replicas 로 복제할 Pod의 갯수를 설정 할 수 있다. 이 예제의 경우 2개의 Pod가 만들어질 것이다. 확인해보자.
# kubectl get pods 
NAME                           READY     STATUS    RESTARTS   AGE
hello-world-86cddf59d5-h5kjd   1/1       Running   0          33m
hello-world-86cddf59d5-hwg64   1/1       Running   0          33m

kubectl descript를 이용해서 pod의 자세한 정보를 확인해보자.
# kubectl describe pod hello-world
Name:               hello-world-86cddf59d5-h5kjd
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               kubenode-01/192.168.56.101
Start Time:         Tue, 14 Aug 2018 21:07:19 +0900
Labels:             pod-template-hash=4278891581
                    run=load-balancer-example
Annotations:        <none>
Status:             Running
IP:                 10.100.1.3
Controlled By:      ReplicaSet/hello-world-86cddf59d5
Containers:
  hello-world:
    Container ID:   docker://99f8da08ca49730a75d613ffb7cbf369232cf85b16a1ea844c6c4cbcdcd531e1
    Image:          gcr.io/google-samples/node-hello:1.0
    Image ID:       docker-pullable://gcr.io/google-samples/node-hello@sha256:d238d0ab54efb76ec0f7b1da666cefa9b40be59ef34346a761b8adc2dd45459b
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 14 Aug 2018 21:07:43 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-85zck (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  default-token-85zck:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-85zck
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age   From                  Message
  ----    ------     ----  ----                  -------
  Normal  Scheduled  37m   default-scheduler     Successfully assigned default/hello-world-86cddf59d5-h5kjd to kubenode-01
  Normal  Pulling    37m   kubelet, kubenode-01  pulling image "gcr.io/google-samples/node-hello:1.0"
  Normal  Pulled     36m   kubelet, kubenode-01  Successfully pulled image "gcr.io/google-samples/node-hello:1.0"
  Normal  Created    36m   kubelet, kubenode-01  Created container
  Normal  Started    36m   kubelet, kubenode-01  Started container

Name:               hello-world-86cddf59d5-hwg64
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               kubenode-02/192.168.56.102
Start Time:         Tue, 14 Aug 2018 21:07:19 +0900
Labels:             pod-template-hash=4278891581
                    run=load-balancer-example
Annotations:        <none>
Status:             Running
IP:                 10.100.2.2
Controlled By:      ReplicaSet/hello-world-86cddf59d5
Containers:
  hello-world:
    Container ID:   docker://59edc50b249f5ae64e38e1550825e0d47a15523a1949ed24f9edf15a53e6841f
    Image:          gcr.io/google-samples/node-hello:1.0
    Image ID:       docker-pullable://gcr.io/google-samples/node-hello@sha256:d238d0ab54efb76ec0f7b1da666cefa9b40be59ef34346a761b8adc2dd45459b
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 14 Aug 2018 21:07:44 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-85zck (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  default-token-85zck:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-85zck
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age   From                  Message
  ----    ------     ----  ----                  -------
  Normal  Scheduled  37m   default-scheduler     Successfully assigned default/hello-world-86cddf59d5-hwg64 to kubenode-02
  Normal  Pulling    37m   kubelet, kubenode-02  pulling image "gcr.io/google-samples/node-hello:1.0"
  Normal  Pulled     36m   kubelet, kubenode-02  Successfully pulled image "gcr.io/google-samples/node-hello:1.0"
  Normal  Created    36m   kubelet, kubenode-02  Created container
  Normal  Started    36m   kubelet, kubenode-02  Started container
Pod의 이름, 실행 중인 노드, IP, Events, Volume 등 중요 정보들을 확인 할 수 있다. 특히 Pod가 제대로 실행되지 않을 때 Events에 있는 내용을 이용해서 Pod를 디버깅 할 수 있다.

Pod는 단일한 서버에 있으므로 실제 배포(deployment)할 때는 하나 이상의 복제를 가지는 ReplicaSet으로 배포한다. 이 Pod와 ReplicaSet으로 이루어진 배포 객체를 Deployments라고 한다. kubectl get deployments 명령을 이용해서, 몇개의 복제를 요청했는지, 몇개의 복제를 사용 할 수 있는지를 확인 할 수 있다.
# kubectl get deployments hello-world
NAME          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-world   2         2         2            2           3m

# kubectl describe deployments hello-world
Name:                   hello-world
Namespace:              default
CreationTimestamp:      Tue, 14 Aug 2018 21:07:19 +0900
Labels:                 run=load-balancer-example
Annotations:            deployment.kubernetes.io/revision=1
Selector:               run=load-balancer-example
Replicas:               2 desired | 2 updated | 2 total | 2 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  run=load-balancer-example
  Containers:
   hello-world:
    Image:        gcr.io/google-samples/node-hello:1.0
    Port:         8080/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   hello-world-86cddf59d5 (2/2 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  4m    deployment-controller  Scaled up replica set hello-world-86cddf59d5 to 2

이렇게 해서 배포 객체까지 만들었다. 이것을 외부에 서비스하기 위해서는 서비스 객체(service object)를 만들어야 한다. MSA(Microservice architecture) 모델을 따르는 애플리케이션들은 하나 이상의 Pod로 구성이 된다. 이들 Pod는 물리적으로 떨어져 있는데, Service로 묶어 줌으로써 하나의 서비스 단위를 만들 수 있다. 예를 들어 프론트엔드와 데이터베이스 백앤드 두 개의 Pod가 있다고 가정해보자. 프론트엔드 Pod는 데이터베이스 Pod를 사용해야 할 것이다. 그러기 위해서는 프론트엔드에서 백엔드 Pod를 찾아서(Service discovery) 연결 할 수 있어야 한다. Service를 이용해서 이런 일들을 할 수 있다.
kubectl expose deployment hello-world --type=NodePort --name=example-service

서비스 정보를 확인해보자.
# kubectl describe services example-service
Name:                     example-service
Namespace:                default
Labels:                   run=load-balancer-example
Annotations:              <none>
Selector:                 run=load-balancer-example
Type:                     NodePort
IP:                       10.98.224.57
Port:                     <unset>  8080/TCP
TargetPort:               8080/TCP
NodePort:                 <unset>  31824/TCP
Endpoints:                10.100.1.3:8080,10.100.2.2:8080
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

curl로 테스트를 했다.
# !curl
curl 10.98.224.57:8080
Hello Kubernetes!

정리

kubernetes 애플리케이션의 Pod, Deployment, Service로 구성되는 논리 구성을 가진다. 이들의 관계를 정리해야 할 것 같다.

 Kubernetes 애플리케이션 구조

관련 문서