2장 테스트 환경 구성하기

2.1.3 베이그런트 구성하고 테스트하기

$ vagrant init      # 프로비저닝을 위한 기초 파일 생성
$ vagrant up        # VagrantFile을 읽어 프로비저닝 진행
$ vagrant halt      # 베이그런트에서 다루는 가상 머신 종료
$ vagrant destroy   # 베이그런트에서 관리하는 가상 머신 삭제
$ vagrant ssh       # 베이그런트에서 관리하는 가상 머신에 ssh로 접속
$ vagrant provision # 베이그런트에서 관리하는 가상 머신에 변경된 설정 적용
$ vagrant port      # 포트 확인

vagrantFile

vm.box = centos/7

box 선택. Discover Vagrant Boxes https://app.vagrantup.com/boxes/search

cfg.vm.network "private_network", ip: "192.168.1.10"

호스트 전용 네트워크를 private_network로 설정해 eth1 인터페이스를 호스트 전용으로 구성한다.

cfg.vm.network "forwarded_port", guest: 22, host: 60010, auto_correct: true, id: "ssh"

ssh 통신은 호스트 60010번을 게스트 22번으로 전달하도록 구성한다. 포트 중복 시 자동 변경.

CentOS

# 실행시간 
$ uptime
09:15:04 up 4 min,  1 user,  load average: 0.00, 0.01, 0.01

# 운영 체제 종류 확인
$ cat /etc/redhat-release
CentOS Linux release 7.8.2003 (Core)

IP 확인

$ ip addr show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:34:7f:49 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.10/24 brd 192.168.1.255 scope global noprefixroute eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe34:7f49/64 scope link 
       valid_lft forever preferred_lft forever

ETC

127.0.0.1로 접속하는 이유?

현재 가상 머신들은 192.168.1.0/24 영역대에 대부분 접속 가능하다. 그러나 현업에서는 데이터 통신과 관리 네트워크를 분리해 사용한다. 127.0.0.1로 접속하면 192.168.1.0/24에서 문제가 발생해도 접속하는 데 문제가 없다.

3장 컨테이너를 다루는 표준 아키텍처, 쿠버네티스

3.1.4 파드 배포를 중심으로 쿠버네티스 구성 요소 살펴보기

파드 조회

$ kubectl get pods --all-namespaces

--all-namespaces는 기본 네임스페이스인 default 외에 모든 것을 표시하겠다는 의미다.

검색

$ kubectl get pods --all-namespaces | grep kube-proxy

kubeadm

사용하는 시스템에 쿠버네티스 클러스터를 자동으로 구성해주는 솔루션

CNI

Container Network Interface는 클라우드 네이티브 컴퓨팅 재단의 프로젝트로 컨테이너 네트워크 안정성과 확장성을 보장하기 위해 개발

네트워크 플러그인

쿠버네티스 클러스터 통신을 위해 선택해야 하는 항목. 일반적으로 CNI로 구성하며 이 책에서는 L3 네트워크인 Calico를 사용.

마스터노드

kubectl

쿠버네티스 클러스터에 명령을 내리는 역할. 다른 구성 요소들과 다르게 바로 실행되는 명령 형태인 바이너리로 배포. 파드의 생성과 상태 관리 및 복구를 담당. kubelet에 문제가 생기면 파드가 제대로 관리되지 않는다.

etcd

구성 요소들의 상태 값이 모두 저장되는 곳. 분산 저장이 가능한 key-value 저장소. 리눅스의 구성정보를 주로 가지고 있는 etc 디렉터리와 distributed의 합성어. 구성 정보를 퍼뜨려 저장하겠다는 의미다.

컨트롤러 매니저

컨트롤러(레플리카 컨트롤러, 서비스 컨트롤러, 볼륨 컨트롤러, 노드 컨트롤러 등)을 생성하고 이를 각 노드에 배포해서 관리

스케줄러

파드, 서비스 등 리소스를 적절한 노드에 할당

워커 노드

kubelet

클러스터의 각 노드에서 실행되는 에이전트. 모든 노드를 대리한다. 마스터 노드에도 있다. Kubelet은 파드에서 컨테이너가 확실하게 동작하도록 관리한다. 파드의 구성 내용(PodSpec)을 받아서 컨테이너 런타임으로 전달하고, 파드 안의 컨테이너들이 정상적으로 작동하는지 모니터링한다. 파드의 상태를 관리한다.

컨테이너 런타임 인터페이스(CRI)

컨테이너 이미지를 가져오고 실행하는 역할. 파드를 이루는 컨테이너를 실행한다. 파드 안에서 다양한 종류의 컨테이너가 문제 없이 작동하게 만드는 표준 인터페이스다.

Containerd

container daemon 테이너를 빌드하고, 실행하고 거기에 네트워크, 스토리지, CLI까지 제공해주는 Docker Engine이라는 패키지가 있었는데, 이게 하나의 패키지로 묶여있다보니 여러 불편함들이 생겨났고 이를 해소하기 위해 여러 사람들이 모여 OCI(Open Container Initiative)라는 Container Runtime 표준을 만들고 이 표준대로 각자 Container Runtime을 만들기 시작했는데 Docker에서 만든 Container Runtime이 바로 containerd (https://containerd.io/)라는 이야기입니다. 참고로 containerd의 d는 daemon의 d입니다. https://kr.linkedin.com/pulse/containerd%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%99%9C-%EC%A4%91%EC%9A%94%ED%95%A0%EA%B9%8C-sean-lee

파드(Pod)

한 개 이상의 컨테이너로 단일 목적으로 일을 하는 단위다. 파드는 언제라도 죽을 수 있는 존재다.

파드 라이프사이클

https://kubernetes.io/ko/docs/concepts/workloads/pods/pod-lifecycle/

kube-proxy

쿠버네티스 클러스터는 파드가 위치한 노드에 kube-proxy를 통해 파드가 통신할 수 있는 네트워크를 설정한다. 이때 실제 통신은 br_netfiler와 iptables로 관리한다. kubelet이 파드의 상태를 관리한다면 kube-proxy는 파드의 통신을 담당한다.

  • 브리지 네트워크를 통과하는 IPv4와 IPv6의 패킷을 iptables가 관리하게 설정한다. 파드의 통신을 iptables로 제어한다. 필요에 따라 IPVS(IP Virtual Server) 같은 방식으로도 구성할 수 있다.
  • br_nefilter 커널 모듈을 사용해 브리지 네트워크를 구성한다. 이때 IP 마스커레이드(Masquerade)를 사용해 내부 네트워크와 외부 네트워크를 분리한다. IP 마스커레이드는 쉽게 설명하면 커널에서 제공하는 NAT(Network Address Translation) 기능으로 이해하면 된다. 실제로는 br_netfilter를 적용하므로써 iptables가 활성화된다.

cgroup 드라이버

리눅스에서, control group은 프로세스에 할당된 리소스를 제한하는데 사용된다.

kubelet과 그에 연계된 컨테이너 런타임 모두 컨트롤 그룹(control group)들과 상호작용 해야 하는데, 이는 파드 및 컨테이너 자원 관리가 수정될 수 있도록 하고 cpu 혹은 메모리와 같은 자원의 요청(request)과 상한(limit)을 설정하기 위함이다. 컨트롤 그룹과 상호작용하기 위해서는, kubelet과 컨테이너 런타임이 _cgroup 드라이버_를 사용해야 한다. 매우 중요한 점은, kubelet과 컨테이너 런타임이 같은 cgroup group 드라이버를 사용해야 하며 구성도 동일해야 한다는 것이다.

두 가지의 cgroup 드라이버가 이용 가능하다.

3.1.5 파드의 생명주기

파드의 생명주기

1) kubectl을 통해 API 서버에 파드 생성을 요청한다. 2) (업데이트가 있을 때마다 매번) API 서버에 전달된 내용이 있으면 API 서버는 etcd에 전달된 내용을 기록해 클러스터 상태 값을 최신으로 유지한다. 3) 컨트롤러 매니저가 API 서버에 파드 생성 요청을 인지하면 파드를 생성하고 이 상태를 API 서버에 전달한다. 이 단계에서는 어떤 워커 노드에 파드를 적용할지 결정하지 않고 파드만 생성한다. 4) 스케줄러가 API 서버에 파드가 생성됐다는 정보를 인지한다. 스케줄러는 생성된 파드를 어떤 워커 노드에 적용할지 조건을 고려해 결정한다. 5) 스케줄러는 kubelet으로 API 서버에 전달된 정보대로 지정한 워커 노드가 파드에 속해 있는지 확인한다. 6) kubelet에서 컨테이너 런타임으로 파드 생성을 요청한다. 7) 파드 생성

  • 쿠버네티스는 작업을 순서대로 진행하는 워크플로 구조가 아니라 선언적인 시스템 구조다. 추구하는 상태를 선언하면 현재 상태와 맞는지 점검한다.

3.2.1 파드를 생성하는 법

yaml

apiVersion: v1  
kind: Pod  
metadata:  
  name: nginx-pod  
spec:  
  containers:  
  - name: container-name  
    image: nginx
$ kubectl create -f ~/_Book_k8sInfra/ch3/3.1.6/nginx-pod.yaml

-f: force가 아니라 filename

$ kubectl get pods -o wide

-o: output

삭제

kubectl delete -f ~/_Book_k8sInfra/ch3/3.1.6/nginx-pod.yaml

run

$ kubectl run nginx-pod --image=nginx

nginx-pod는 파일명이 아니라 임의로 정한 이름 run으로 파드를 생성하면 단일 파드 1개만 생성되고 관리된다.

create

$ kubectl create deployment dpy-nginx --image=nginx

create deploymen로 파드를 생성하면 디플로이먼트라는 그룹 내에서 파드가 생성된다. run이 초코파이 1개라면 create deployment는 상자에 들어있는 초코파이 1개다.

$ kubectl delete deployment dpy-nginx

3.2.2 오브젝트란

쿠버네티스 오브젝트는 쿠버네티스 시스템에서 영속성을 가지는 오브젝트이다. 쿠버네티스는 클러스터의 상태를 나타내기 위해 이 오브젝트를 이용한다. 스펙과 상태 값을 가진다. 파드, 네임스페이스, 볼륨, 서비스, 디플로이먼트, 데몬셋, 컨피그맵, 레플리카셋, PV, PVC, 스테이트풀셋 등이 있다.

기본 오브젝트

파드

컨테이너를 포함하는 배포단위 파드 내 컨테이너들은 IP와 포트를 공유한다. 파드 내 컨테이너들은 디스크 볼륨을 공유할 수 있다.

네임스페이스

쿠버네티스 클러스터에 사용되는 리소스들을 구분해 관리하는 그룹이다. 특별히 지정하지 않으면 default, 쿠버네티스 시스템에 사용되는 kube-system, 온프레미스에서 쿠버네티스를 사용할 경우 외부에서 쿠버네티스 클러스터 내부로 접속하게 도와주는 컨테이너들이 속한 metallb-system 등이 있다.

볼륨

컨테이너의 외장 디스크라고 생각하면 된다. 파드를 기동할 때 컨테이너에 마운트해서 사용한다. 파드가 사라지더라도 디렉터리 볼륨 오브젝트를 통하면 저장이 가능하다. 파드가 생성될 때 파드에서 사용할 수 있는 디렉터리를 제공한다. 기본적으로 파드는 영속되는 개념이 아니라 제공되는 디렉터리도 임시로 사용한다.

서비스

파드 집합에서 실행중인 애플리케이션을 네트워크 서비스로 노출하는 추상화 방법. 파드는 클러스터 내에서 유동적이기 때문에 접속 정보가 고정적일 수 없다. 따라서 서비스를 통해 내/외부로 연결된다. 서비스는 새로운 파드가 생성될 때 부여되는 새로운 IP를 기존에 제공하던 기능과 연결한다. 기존 인프라에서 로드밸런서, 게이트웨이와 비슷한 역할이다.

디플로이먼트

디플로이먼트(Deployment) 는 파드와 레플리카셋(ReplicaSet)에 대한 선언적 업데이트를 제공한다. 디플로이먼트를 사용하면 레플리카셋의 변경 사항을 저장하는 Revision을 남기기 때문에 Rollback 이 가능해지고 여러 무중단 배포 전략을 취할 수 있게 된다. 파드 생성을 감시하는 게 아니라 레플리카셋을 포함하는 오브젝트의 생성을 감시한다. Deployment는 Pod 배포를 위해서 RC를 생성하고 관리하는 역할을 하며, 특히 롤백을 위한 기존 버전의 RC 관리등 여러가지 기능을 포괄적으로 포함하고 있다.

3.2.3 레플리카셋으로 파드 수 관리하기

$ kubectl scale pod nginx-pod --replicas=3
Error from server (NotFound): the server could not find the requested resource

nginx는 디플로이먼트가 아니라 파드로 생성했기 때문에 찾을 수 없다는 메시지가 나온다.

$ kubectl scale deployment dpy-nginx --replicas=3
deployment.apps/dpy-nginx created

3.2.4 스펙을 지정해 오브젝트 생성하기

apiVersion: apps/v1  
kind: Deployment  
metadata:  
  name: echo-hname  
  labels:  
    app: nginx  
spec:  
  replicas: 3  
  selector:  
    matchLabels:  
      app: nginx  
  template:  
    metadata:  
      labels:  
        app: nginx  
    spec:  
      containers:  
      - name: echo-hname  
        image: sysnet4admin/echo-hname

apps/v1은 여러 종류의 kind(오브젝트)를 가지고 있는데 그 중에서 Deployment를 선택해 레플리카셋을 생성한다.

사용가능한 버전 확인

$ kubectl api-versions
admissionregistration.k8s.io/v1
admissionregistration.k8s.io/v1beta1
apiextensions.k8s.io/v1
...
$ kubectl create -f ~/_Book_k8sInfra/ch3/3.2.4/echo-hname.yaml
deployment.apps/echo-hname created

yaml 파일의 레플리카 수를 커맨드라인에서 변경

$ sed -i 's/replicas: 3/replicas: 6/' ~/_Book_k8sInfra/ch3/3.2.4/echo-hname.yaml
  • -i: --in-place의 약어로 변경한 내용을 현재 파일에 바로 적용하겠다는 의미
  • s/: 주어진 패턴을 원하는 패턴으로 변경하겠다는 의미

확인

$ cat ~/_Book_k8sInfra/ch3/3.2.4/echo-hname.yaml | grep replicas
replicas: 6

변경된 내용 적용

$ kubectl create -f ~/_Book_k8sInfra/ch3/3.2.4/echo-hname.yaml
Error from server (AlreadyExists): error when creating "/root/_Book_k8sInfra/ch3/3.2.4/echo-hname.yaml": deployments.apps "echo-hname" already exists

create로 생성했으므로 변경 사항을 바로 적용할 수 없다.

3.2.5 apply로 오브젝트 생성하고 관리하기

변경사항이 발생할 가능성이 있는 오브젝트는 apply로 생성하는 것이 좋다.

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.2.4/echo-hname.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment.apps/echo-hname configured

처음부터 apply로 생성한 게 아니어서 경고가 뜬다.

일회적 사용: create 변경 가능성이 있는 오브젝트: apply 단일 파드 : run

3.2.6 파드의 컨테이너 자동 복구 방법

파드의 IP 확인

$ kubectl get pods -o wide

exec 명령으로 파드 컨테이너의 셸(shell)에 접속

$ kubectl exec -it nginx-pod -- /bin/bash
  • exec: execute
  • -i: stdin(standard input, 표준 입력)
  • -t: tty(teletypewriter)
  • --: 명령어를 구분할 때 사용

PID 확인

$ cat /run/nginx.pid
1

생성시간 확인

$ ls -l /run/nginx.pid

다른 터미널을 열어서 1초마다 한 번씩 요청을 보낸다.

$ i=1; while true; do sleep 1; echo $((i++)) 'curl --silent 172.16.103.132 | grep title' ; done
1 curl --silent 172.16.103.132 | grep title
2 curl --silent 172.16.103.132 | grep title
3 curl --silent 172.16.103.132 | grep title
4 curl --silent 172.16.103.132 | grep title
...

원래 터미널 창에서

$ kill 1
command terminated with exit code 137

프로세스는 몇 초만에 종료되고 다시 실행된다. 다시 한 번 bash에 접속에서 nginx.pid 생성시간을 통해 새로 생성한 프로세스인지 확인

3.2.7

디플로이먼트에 속한 파드가 아니면 어떤 컨트롤러도 이 파드를 관리하지 않는다. 반대로 디플로이먼트에 속한 파드는 replicas에 선언한 수대로 파드 수를 유지한다.

3.2.8 노드 자원 보호하기

cordon

cordon은 노드의 현재 상태를 보존한다.

w3-k8s 노드에서 문제가 자주 발생해서 현재 상태 보존

$ kubectl cordon w3-k8s
node/w3-k8s cordoned
$ kubectl get nodes
NAME     STATUS                     ROLES    AGE    VERSION
m-k8s    Ready                      master   166m   v1.18.4
w1-k8s   Ready                      <none>   163m   v1.18.4
w2-k8s   Ready                      <none>   161m   v1.18.4
w3-k8s   Ready,SchedulingDisabled   <none>   158m   v1.18.4

w3-k8s를 더이상 파드가 할당되지 않는 상태로 변경

파드 수 9개로 늘린다

$ kubectl scale deployment echo-hname --replicas=9
deployment.apps/echo-hname scaled

커스텀 컬럼 확인

  • -o: output
  • custom-colums: 사용자가 임의로 구성할 수 있는 열
    • NAME, IP, STATUS, NODE: 열의 제목
    • 콜론(:) 뒤에 내용 값: .metadata.name, status.podIP... ```shell $ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:.spec.nodeName

echo-hname-7894b67f-7d924 172.16.221.138 Running w1-k8s echo-hname-7894b67f-7fbkn 172.16.221.139 Running w1-k8s echo-hname-7894b67f-g8d7n 172.16.103.133 Running w2-k8s echo-hname-7894b67f-h6rb8 172.16.103.138 Running w2-k8s echo-hname-7894b67f-l4j6v 172.16.103.137 Running w2-k8s echo-hname-7894b67f-rvcv9 172.16.132.6 Running w3-k8s echo-hname-7894b67f-xbsl7 172.16.221.134 Running w1-k8s echo-hname-7894b67f-z4965 172.16.221.137 Running w1-k8s echo-hname-7894b67f-zcgbb 172.16.103.136 Running w2-k8s

파드 수가 w3-k8s만 여전히 1개인 것을 볼 수 있다.

할당되지 않게 만들었던 설정 해제
```shell
$ kubectl uncordon w3-k8s
node/w3-k8s uncordoned

배포된 파드의 세부 값 확인하는 방법

$ kubectl get pod echo-hname-7894b67f-6fkbk -o yaml > pod.yaml

3.2.9 노드 유지보수하기

유지보수를 하는 등 노드를 꺼야 하는 상황에 대비해 drain 기능 제공. drain은 노드에서 파드를 삭제하고 다른 곳에서 다시 생성한다. 지정된 노드의 파드를 전부 다른 곳으로 이동시켜 해당 노드를 유지보수할 수 있게 한다.

$ kubectl drain w3-k8s
node/w3-k8s cordoned
error: unable to drain node "w3-k8s", aborting command...

There are pending nodes to be drained:
 w3-k8s
error: cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): kube-system/calico-node-rprsd, kube-system/kube-proxy-bcs89

그런데 DaemonSet은 각 노드에 1개만 존재하는 파드라서 drain으로는 삭제할 수 없다.

ignore-daemonsets 옵션을 사용하면 DaemonSet은 무시하고 진행한다.

$ kubectl drain w3-k8s --ignore-daemonsets
node/w3-k8s already cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-rprsd, kube-system/kube-proxy-bcs89
evicting pod default/echo-hname-7894b67f-rvcv9
pod/echo-hname-7894b67f-rvcv9 evicted
node/w3-k8s evicted

노드 w3-k8s에 파드가 없는지 확인

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:.spec.nodeName
NAME                        IP               STATUS    NODE
echo-hname-7894b67f-g8d7n   172.16.103.133   Running   w2-k8s
echo-hname-7894b67f-jwfp9   172.16.103.139   Running   w2-k8s
echo-hname-7894b67f-xbsl7   172.16.221.134   Running   w1-k8s

w3-k8s의 SchedulingDisabled 상태 확인

$ kubectl get nodes
NAME     STATUS                     ROLES    AGE     VERSION
m-k8s    Ready                      master   4h34m   v1.18.4
w1-k8s   Ready                      <none>   4h31m   v1.18.4
w2-k8s   Ready                      <none>   4h29m   v1.18.4
w3-k8s   Ready,SchedulingDisabled   <none>   4h26m   v1.18.4

유지보수가 끝났다고 가정하고 uncordon 명령 실행

$ kubectl uncordon w3-k8s
node/w3-k8s uncordoned

3.2.10 파드 업데이트하고 복구하기

컨테이너에 새로운 기능을 추가하거나 버그가 발생해 버전을 업데이트해야 할 때가 있다. 또는 기존 버전으로 복구해야 하는 일도 발생한다.

파드 업데이트하기

--record는 배포한 정보의 히스토리를 기록한다.

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.2.10/rollout-nginx.yaml --record

record 옵션으로 기록된 히스토리는 rollout history 명령을 실행해 확인할 수 있다.

$ kubectl rollout history deployment rollout-nginx

배포한 파드의 정보를 확인

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:.spec.nodeName
NAME                             IP               STATUS    NODE
rollout-nginx-64dd56c7b5-6g9p5   172.16.221.140   Running   w1-k8s
rollout-nginx-64dd56c7b5-f65lh   172.16.132.9     Running   w3-k8s
rollout-nginx-64dd56c7b5-llmcw   172.16.103.140   Running   w2-k8s

배포된 파드에 속한 nginx 버전 확인

$ curl -I --silent 172.16.103.140 | grep Server
Server: nginx/1.15.12
  • -I : 헤더 정보만 보여주는 옵션

set image 명령으로 nginx 버전 업데이트

$ kubectl set image deployment rollout-nginx nginx=nginx:1.16.0 --record
deployment.apps/rollout-nginx image updated

업데이트한 후 파드의 상태 확인

$ kubectl get pods
NAME                             IP               STATUS    NODE
rollout-nginx-8566d57f75-g96xl   172.16.221.141   Running   w1-k8s
rollout-nginx-8566d57f75-hg6fw   172.16.103.141   Running   w2-k8s
rollout-nginx-8566d57f75-w8msr   172.16.132.10    Running   w3-k8s

파드의 이름과 IP가 바뀌었다. 파드는 언제라도 지우고 다시 만들 수 있기 때문에 순차적으로 지우고 다시 생성한다. 파드 수가 많을 때는 하나씩이 아니라 다수의 파드가 업데이트된다. 기본값은 1/4(25%)개이며 최솟값은 1개다.

Deployment 상태 확인

$ kubectl rollout status deployment rollout-nginx
deployment "rollout-nginx" successfully rolled out

rollout-nginx에 적용된 명령들을 확인

$ kubectl rollout history deployment rollout-nginx
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=/root/_Book_k8sInfra/ch3/3.2.10/rollout-nginx.yaml --record=true
2         kubectl set image deployment rollout-nginx nginx=nginx:1.16.0 --record=true

업데이트 확인

$ curl -I --silent 172.16.132.10 | grep Server
Server: nginx/1.16.0

업데이트 실패 시 파드 복구

nginx 컨테이너 버전을 의도(1.17.2)와 다르게 1.17.23으로 입력

$ kubectl set image deployment rollout-nginx nginx=nginx:1.17.23
deployment.apps/rollout-nginx image updated

파드가 삭제되지 않고 Pending(대기 중) 상태에서 넘어가지 않는다.

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:.spec.nodeName
NAME                             IP               STATUS    NODE
rollout-nginx-8566d57f75-2nv4z   172.16.103.143   Running   w2-k8s
rollout-nginx-8566d57f75-g96xl   172.16.221.142   Running   w1-k8s
rollout-nginx-8566d57f75-hg6fw   172.16.103.142   Running   w2-k8s
rollout-nginx-856f4c79c9-4frlq   172.16.132.13    Pending   w3-k8s

어떤 문제인지 상태 확인

$ kubectl rollout status deployment rollout-nginx
Waiting for deployment "rollout-nginx" rollout to finish: 1 out of 3 new replicas have been updated...

새로운 replicas는 생성했으나(new replicas have been updated) 플로이먼트를 배포하는 단계에서 대기 중인것을 확인

describe로 문제점 자세히 확인

$ kubectl describe deployment rollout-nginx
Name:                   rollout-nginx
Namespace:              default
CreationTimestamp:      Wed, 08 Feb 2023 19:24:11 +0900
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 3
                        kubernetes.io/change-cause: kubectl set image deployment rollout-nginx nginx=nginx:1.16.0 --record=true
Selector:               app=nginx
Replicas:               3 desired | 1 updated | 4 total | 3 available | 1 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.17.23
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    ReplicaSetUpdated
OldReplicaSets:  rollout-nginx-8566d57f75 (3/3 replicas created)
NewReplicaSet:   rollout-nginx-856f4c79c9 (1/1 replicas created) ## 여기

replicas가 새로 생성되는 과정에 멈춰 있다. 버전을 잘못 사용했기 때문.

정상상태로 복구하기 위해 rollout history를 사용해서 업데이트할 때 사용했던 명령어들을 확인

$ kubectl rollout history deployment rollout-nginx
deployment.apps/rollout-nginx 
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=/root/_Book_k8sInfra/ch3/3.2.10/rollout-nginx.yaml --record=true
2         kubectl set image deployment rollout-nginx nginx=nginx:1.16.0 --record=true
3         kubectl set image deployment rollout-nginx nginx=nginx:1.17.23 --record=true

rollout undo로 명령 실행을 취소해 마지막 단계(revision 3)에서 전 단계(revision 2)로 상태를 되돌린다.

$ kubectl rollout history deployment rollout-nginx
deployment.apps/rollout-nginx 
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=/root/_Book_k8sInfra/ch3/3.2.10/rollout-nginx.yaml --record=true
3         kubectl set image deployment rollout-nginx nginx=nginx:1.17.23 --record=true
4         kubectl set image deployment rollout-nginx nginx=nginx:1.16.0 --record=true

revision 4가 추가되고 revision 2가 삭제됐다.

확인

$ kubectl describe deployment rollout-nginx
Name:                   rollout-nginx
Namespace:              default
CreationTimestamp:      Wed, 08 Feb 2023 19:24:11 +0900
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 4
                        kubernetes.io/change-cause: kubectl set image deployment rollout-nginx nginx=nginx:1.16.0 --record=true
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.16.0
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   rollout-nginx-8566d57f75 (3/3 replicas created) ## 여기

특정 시점으로 파드 복구하기

처음 상태인 revision 1로 돌아가자

$ kubectl rollout undo deployment rollout-nginx --to-revision=1
deployment.apps/rollout-nginx rolled back

새로 생성된 파드 IP 확인

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:.spec.nodeName
NAME                             IP               STATUS    NODE
rollout-nginx-64dd56c7b5-kq6nt   172.16.221.143   Running   w1-k8s
rollout-nginx-64dd56c7b5-x6c74   172.16.132.14    Running   w3-k8s
rollout-nginx-64dd56c7b5-xk7pr   172.16.103.144   Running   w2-k8s

처음 상태 버전 확인

$ curl -I --silent 172.16.221.143 | grep Server
Server: nginx/1.15.12

3.3 쿠버네티스 연결을 담당하는 서비스

3.3.1 간단하게 연결하는 노드포트

노드포트 서비스로 외부에서 접속하기

디플로이먼트로 파드 생성

$ kubectl create deployment np-pods --image=sysnet4admin/echo-hname
deployment.apps/np-pods created

kubectl create로 노드포트 서비스를 생성

$ kubectl create -f ~/_Book_k8sInfra/ch3/3.3.1/nodeport.yaml

nodeport.yaml

apiVersion: v1  
kind: Service  
metadata:  
  name: np-svc  
spec:  
  selector:  
    app: np-pods  ##
  ports:  
    - name: http  
      protocol: TCP  
      port: 80  # 서비스 포트
      targetPort: 80  # 컨테이너 포트
      nodePort: 30000  # 외부 접속 포트
  type: NodePort

포트 30000으로 접속하면 알아서 파드들에 부하가 분산되는데 이는 노드포트의 오브젝트 스펙에 적힌 app(np-pods) 이름과 디플로이먼트 이름을 확인해 동일하면 같은 파드라고 간주하기 때문이다.

$ kubectl get nodes -o wide
NAME     STATUS   ROLES    AGE    VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION                CONTAINER-RUNTIME
m-k8s    Ready    master   3d2h   v1.18.4   192.168.1.10    <none>        CentOS Linux 7 (Core)   3.10.0-1127.19.1.el7.x86_64   docker://1.13.1
w1-k8s   Ready    <none>   3d2h   v1.18.4   192.168.1.101   <none>        CentOS Linux 7 (Core)   3.10.0-1127.19.1.el7.x86_64   docker://1.13.1
w2-k8s   Ready    <none>   3d2h   v1.18.4   192.168.1.102   <none>        CentOS Linux 7 (Core)   3.10.0-1127.19.1.el7.x86_64   docker://1.13.1
w3-k8s   Ready    <none>   3d2h   v1.18.4   192.168.1.103   <none>        CentOS Linux 7 (Core)   3.10.0-1127.19.1.el7.x86_64   docker://1.13.1
w4-k8s   Ready    <none>   3d2h   v1.18.4   192.168.1.104   <none>        CentOS Linux 7 (Core)   3.10.0-1127.19.1.el7.x86_64   docker://1.13.1

브라우저에서 접속: INTERNAL-IP:30000 -> 192.168.1.101:30000

expose로 노드포트 서비스 생성하기

expose 명령을 사용해 서비스로 내보낼 디플로이먼트를 np-pods로 지정

$ kubectl expose deployment np-pods --type=NodePort --name=np-svc-v2 --port=80
ervice/np-svc-v2 exposed

오브젝트 스펙으로 생성할 때 노드포트 포트를 30000으로 지정했으나 expose를 사용하면 30000~32767에서 임의로 지정된다.

$ kubectl get services
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP        21h
np-svc       NodePort    10.103.232.204   <none>        80:30000/TCP   31m
np-svc-v2    NodePort    10.104.253.116   <none>        80:32663/TCP   24

브라우저에서 접속: INTERNAL-IP:32663 -> 192.168.1.104:32336

명령어 정리

$ kubectl create deployment <deployment 이름> --image=nginx

$ kubectl expose deployment aa --port=80 --target-port=80 --type=NodePort --name=aa-svc

# kubectl expose deployment <deployment 이름> --port=서비스포트 --target-port=컨테이너포트 --type=NodePort --name=서비스 이름

$ kubectl get nodes -o wide
# INTERNAL IP 확인

$ kubectl get svc
# 외부 접속 PORT 확인
  • 노드포트는 서비스 API 오브젝트에서 사용하는 스펙 중의 하나 (포트니까 L4?)
  • 인그레스는 API 오브젝트 (L7)

3.3.2 사용 목적별로 연결하는 인그레스

클러스터 내의 서비스에 대한 외부 접근을 관리하는 API 오브젝트이며, 일반적으로 HTTP를 관리함. 인그레스는 부하 분산, SSL 종료, 명칭 기반의 가상 호스팅을 제공할 수 있다.

인그레스는 클러스터 외부에서 클러스터 내부 서비스로 HTTP와 HTTPS 경로를 노출한다. 트래픽 라우팅은 인그레스 리소스에 정의된 규칙에 의해 컨트롤된다.

언제 인그레스를 사용해야 할까?

노트포트는 중복 사용할 수 없어서 1개의 노드포트에 1개의 디플로이먼트만 적용된다. 여러 개의 디플로이먼트가 있을 때 그 수만큼 노드포트 서비스를 구동하지 않고 인그레스를 사용한다. 인그레스는 고유한 주소를 통해 응답을 제공하고 L4/L7 로드밸런서와 보안 인증서를 처리하는 기능을 제공한다.

인그레스 컨트롤러는 파드와 직접 통신할 수 없어서 노드포트 또는 로드밸런서 서비스와 연동되어야 한다.

  • 1) 사용자는 노드포트로 서비스에 접속한다. 이때 노드포트 서비스를 NGINX 인그레스 컨트롤러로 구성한다.
  • 2) NGINX 인그레스 컨트롤러는 사용자의 접속 경로에 따라 적합한 클러스터 IP 서비스로 경로를 제공한다.
  • 3) 클러스터 IP 서비스는 사용자를 해당 파드로 연결한다.
$ kubectl create deployment in-hname-pod --image=sysnet4admin/echo-hname
deployment.apps/in-hname-pod created

$ kubectl create deployment in-ip-pod --image=sysnet4admin/echo-ip
deployment.apps/in-ip-pod created

$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
in-hname-pod-8565c86448-tbpwd   1/1     Running   0          60s
in-ip-pod-76bf6989d-sgt9x       1/1     Running   0          20s
$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.2/ingress-nginx.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created

NGINX 컨트롤러 버전 참고: https://github.com/kubernetes/ingress-nginx#support-versions-table

아래와 같이 생성해도 된다.

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole unchanged
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding unchanged
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created

NGINX 인그레스 컨트롤러는 default 네임스페이스가 아닌 ingress-nginx 네임스페이스에 속한다. -n은 namesapce의 약어로 default 외의 네임스페이스를 확인할 때 사용하는 옵션이다.

$ kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-5bb8fb4bb6-jdtf7   1/1     Running   0          56s

인그레스를 사용자 요구사항에 맞게 설정하려면 경로와 작동을 정의해야 한다.

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.2/ingress-config.yaml
ingress.networking.k8s.io/ingress-nginx created

ingress-config.yaml

apiVersion: networking.k8s.io/v1beta1  
kind: Ingress  
metadata:  
  name: ingress-nginx  
  annotations:  
    nginx.ingress.kubernetes.io/rewrite-target: /  
spec:  
  rules:  
  - http:  
      paths:  
      - path:  
        backend:  
          serviceName: hname-svc-default  
          servicePort: 80  
      - path: /ip  
        backend:  
          serviceName: ip-svc  
          servicePort: 80  
      - path: /your-directory  
        backend:  
          serviceName: your-svc  
          servicePort: 80

hname-svc-default이 어디서 나오나 했더니 뒤에서… 서비스로 노출한다.

외부에서 주소 값과 노드포트를 가지고 들어 오는 것은 hname-svc-default 서비스와 연결된 파드로 넘기고 외부에서 들어오는 주소 값, 노드포트와 함께 뒤에 /ip를 추가한 주소 값은 ip-svc 서비스와 연결된 파드로 접속하게 설정했다.

ingress 설정 확인

$ kubectl get ingress
NAME            CLASS    HOSTS   ADDRESS   PORTS   AGE
ingress-nginx   <none>   *                 80      148m

인그레스에 적용된 설정을 yaml 형식으로 확인

$ kubectl get ingress -o yaml
piVersion: v1
items:
- apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
...

외부에서 NGINX 인그레스 컨트롤러에 접속할 수 있게 노드포트 서비스로 NGINX 인그레스 컨트롤러를 외부에 노출

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.2/ingress.yaml
service/nginx-ingress-controller created

ingress.yaml

apiVersion: v1  
kind: Service  
metadata:  
  name: nginx-ingress-controller  
  namespace: ingress-nginx  
spec:  
  ports:  
  - name: http  
    protocol: TCP  
    port: 80  
    targetPort: 80  
    nodePort: 30100  
  - name: https  
    protocol: TCP  
    port: 443  
    targetPort: 443  
    nodePort: 30101  
  selector:  
    app.kubernetes.io/name: ingress-nginx  
  type: NodePort
$ kubectl get services -n ingress-nginx
NAME                       TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
nginx-ingress-controller   NodePort   10.105.237.28   <none>        80:30100/TCP,443:30101/TCP   4m13s

expose 명령으로 디플로이먼트도 서비스로 노출

$ kubectl expose deployment in-hname-pod --name=hname-svc-default --port=80,443
service/hname-svc-default exposed
$ kubectl expose deployment in-ip-pod --name=ip-svc --port=80,443
service/ip-svc exposed

명령어 정리

# deployment 생성
$ kubectl create deployment aa --image=nginx
$ kubectl create deployment bb --image=nginx

# deployment 서비스로 노출
$ kubectl expose deployment aa --name=aa-svc --port=80,443
$ kubectl expose deployment bb --name=bb-svc --port=80,443

# NGINX controller 생성 (Pod)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml

# 조회
kubectl get po -n ingress-nginx

# NGINX controlloer 외부로 노출
$ kubectl expose po -n ingress-nginx nginx-ingress-controller-5bb8fb4bb6-9rfwb --port=80 --target-port=80 --type=NodePort --name=http-svc
$ kubectl expose po -n ingress-nginx nginx-ingress-controller-5bb8fb4bb6-9rfwb --port=443 --target-port=443 --type=NodePort --name=https-svc

# 조회
$ kubectl get services -n ingress-nginx

# Ingress 생성
$ kubectl apply -f ingress-config.yaml

# Ingress 조회
$ kubectl get ingress -o yaml

ingress-config.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-nginx
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /aa
        backend:
          serviceName: aa-svc
          servicePort: 80
      - path: /bb
        backend:
          serviceName: bb-svc
          servicePort: 80

3.3.3 클라우드에서 쉽게 구성 가능한 로드밸런서

로드밸런서는 퍼블릭 클라우드에서 제공하는 쿠버네티스를 사용한다면 아래의 명령어로 선언만 하면 된다. 하지만 온프레미스 환경에서는 사용할 수 없다.

$ kubectl expose deployment ex-lb --type=LoadBalancer --name=ex-svc

3.3.4 온프레미스에서 로드밸런서를 제공하는 MetalLB

온프레미스에서 로드밸런서를 사용하려면 로드밸런서 서비스를 받아주는 구성이 필요하다. 이를 지원하는 것이 MetalLB이다.

MetalLB는 베어메탈(운영체제가 설치되지 않은 하드웨어)로 구성된 쿠버네티스에서 사용할 수 있게 고안된 로드밸런서다. L2 네트워크(MAC 주소), L3 네트워크(IP)로 로드밸런서를 구현한다.

로드밸런서를 띄우기 위한 사전작업

$ kubectl create deployment lb-hname-pods --image=sysnet4admin/echo-hname
deployment.apps/lb-hname-pods created

$ kubectl scale deployment lb-hname-pods --replicas=3
deployment.apps/lb-hname-pods scaled

$ kubectl create deployment lb-ip-pods --image=sysnet4admin/echo-ip
deployment.apps/lb-ip-pods created

$ kubectl scale deployment lb-ip-pods --replicas=3
deployment.apps/lb-ip-pods scaled
$ kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
in-ip-pod-76bf6989d-sgt9x        1/1     Running   0          4h10m
lb-hname-pods-79b95c7c7b-fghlw   1/1     Running   0          2m49s
lb-hname-pods-79b95c7c7b-kx9zx   1/1     Running   0          2m49s
lb-hname-pods-79b95c7c7b-tj2xw   1/1     Running   0          2m57s
lb-ip-pods-6c6bb59b4-gk6v4       1/1     Running   0          2m38s
lb-ip-pods-6c6bb59b4-mdjc4       1/1     Running   0          2m27s
lb-ip-pods-6c6bb59b4-mswfz       1/1     Running   0          2m27s
$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.4/metallb.yaml
namespace/metallb-system created
podsecuritypolicy.policy/speaker created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/config-watcher created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/config-watcher created
daemonset.apps/speaker created
deployment.apps/controller created
$ kubectl get pods -n metallb-system -o wide
NAME                          READY   STATUS    RESTARTS   AGE    IP              NODE     NOMINATED NODE   READINESS GATES
controller-5f98465b6b-fj4wj   1/1     Running   0          113s   172.16.132.18   w3-k8s   <none>           <none>
speaker-52lfb                 1/1     Running   0          113s   192.168.1.102   w2-k8s   <none>           <none>
speaker-j5l2j                 1/1     Running   0          113s   192.168.1.101   w1-k8s   <none>           <none>
speaker-xfwlt                 1/1     Running   0          113s   192.168.1.103   w3-k8s   <none>           <none>
speaker-xndlq                 1/1     Running   0          113s   192.168.1.10    m-k8s    <none>           <none>

ConfigMap 설정

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.4/metallb-l2config.yaml 
configmap/config created

metallb-l2config.yaml

apiVersion: v1  
kind: ConfigMap  
metadata:  
  namespace: metallb-system  
  name: config  
data:  
  config: |  
    address-pools:  
    - name: nginx-ip-range  
      protocol: layer2  
      addresses:  
      - 192.168.1.11-192.168.1.13

ConfigMap 생성 확인

$ kubectl get configmap -n metallb-system
NAME     DATA   AGE
config   1      85s

MetalLB 설정 적용 확인

$ kubectl get configmap -n metallb-system -o yaml
apiVersion: v1
items:
- apiVersion: v1
  data:
    config: |
      address-pools:
      - name: nginx-ip-range
        protocol: layer2
        addresses:
        - 192.168.1.11-192.168.1.13
  kind: ConfigMap
...

각 디플로이먼트(lb-hname-pods, lb-ip-pods)를 로드밸런서 서비스로 노출

$ kubectl expose deployment lb-hname-pods --type=LoadBalancer --name=lb-hname-svc --port=80
service/lb-hname-svc exposed
$ kubectl expose deployment lb-ip-pods --type=LoadBalancer --name=lb-ip-svc --port=80
service/lb-ip-svc exposed

확인

$ kubectl get services

명령어 정리

# deployment 생성, 레플리카 수 늘리기
$ kubectl create deployment aa --image=nginx
$ kubectl create deployment bb --image=nginx
$ kubectl scale deployment aa --replicas=3
$ kubectl scale deployment bb --replicas=3

# metallb 생성 (pod)
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.8.3/manifests/metallb.yaml

# 배포된 metallb controller, speaker 조회
$ kubectl get pods -n metallb-system -o wide

# ConfigMap에 metallb-system을 등록
$ kubectl apply -f metallb-config.yaml

# ConfigMap 조회
$ kubectl get configmap -n metallb-system -o yaml

# deployment를 로드밸런서 서비스로 노출
$ kubectl expose deployment aa --type=LoadBalancer --name=lb-aa-svc --port=80
$ kubectl expose deployment bb --type=LoadBalancer --name=lb-bb-svc --port=80

# EXTERNAL-IP가 잘 적용됐는지 확인
$ kubectl get svc

metallb-config.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: nginx-ip-range
      protocol: layer2
      addresses:
      - 192.168.1.101-192.168.1.103

3.3.5 부하에 따라 자동으로 파드 수를 조절하는 HPA

부하량에 따라 디플로이먼트의 파드 수를 유동적으로 관리하는 기능을 제공한다. 이를 HPA(Horizontal Pod Autoscaler)라고 한다.

수평 스케일링은 부하 증가에 대해 파드를 더 배치하는 것을 뜻한다.

Horizontal Pod Autoscaling을 활용하는 연습 예제가 존재한다.

디플로이먼트 생성

$ kubectl create deployment hpa-hname-pods --image=sysnet4adin/echo-hname
deployment.apps/hpa-hname-pods created

hpa-hname-pods를 앞서 ConfigMap으로 설정한 로드밸런서 서비스로 expose로 실행

$ kubectl expose deployment hap-hname-pods --type=LoadBalancer --name=hpa-hname-svc --port=80
service/hpa-hname-svc exposed
$ kubectl get services
NAME            TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)        AGE
hpa-hname-svc   LoadBalancer   10.100.8.64   192.168.1.11   80:31751/TCP   9m59s
kubernetes      ClusterIP      10.96.0.1     <none>         443/TCP        26h

파드의 자원이 어느 정도 사용되는지 파악 top(table of processes)과 비슷한 kubectl top pods 실행

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

HPA가 자원을 요청할 때 메트릭 서버를 통해 계측값을 전달받는다. 그런데 메트릭 서버가 없기 때문에 에러가 나온다.

쿠버네티스 메트릭 서버의 원본 소스를 sysnet4admin 계정으로 옮겨 메트릭 서버 생성

$ kubectl create -f ~/_Book_k8sInfra/ch3/3.3.5/metrics-server.yaml
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.apps/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

metrics-server.yaml

#Main_Source_From:

- https://github.com/kubernetes-sigs/metrics-server

원본 소스에서 일부 코드를 변경함

```shell
$ kubectl top pods
NAME                        CPU(cores)   MEMORY(bytes)   
in-ip-pod-76bf6989d-sgt9x   0m           2Mi 

edit 명령으로 deployment 수정

$ kubectl edit deployment hpa-hname-pods
deployment.apps/aa edited

m은 milliunits의 약어로 1000m은 1개의 CPU다. 따라서 10m은 파드의 CPU 0.01 사용을 기준으로 파드를 증설하게 설정한 것이다.

115번 라인 아래 resources = {}에 {}를 지우고 아래 내용으로 채우면 된다. 띄어쓰기를 잘 해야 함

spec:
  containers:
  - iamge: <이미지 이름>
	name: <deployment 이름>
	resources:
	  requests:
	    cpu: "10m"
	  limits:
	    cpu: "50m"
	terminationMessagePath: ..

다시 조회를 해보면 스펙이 변경돼 새로운 파드가 생성된 것을 확인할 수 있다.

$ k top pods
NAME                  CPU(cores)   MEMORY(bytes)   
aa-7895d64877-hvlmm   0m           1Mi 

autoscale을 설정해서 특정 조건이 만족되는 경우에 자동으로 scale 명령 수행하게 만들 수 있다. min은 최소 파드의 수, max는 최대 파드 수. cpu-percent는 CPU 사용량이 50%를 넘으면 autoscale하겠다는 의미다.

$ kubectl autoscale deployment hpa-hname-pods --min=1 --max=30 --cpu-percent=50
horizontalpodautoscaler.autoscaling/hpa-hname-pods autoscaled

마스터 노드 창 두 개를 띄워서 확인

$ watch kubectl top pods
$ watch kubectl get pods

로컬 파워셸에서 부하를 주는 명령 실행

#!/bin/powershell  
Param (  
[Parameter(Mandatory=$true)]  
$IPwPort  
)  
  
$i=0; while($true)  
{  
  % { $i++; write-host -NoNewline "$i $_" }  
  (Invoke-RestMethod "http://$IPwPort")-replace '\n', " "  
}

부하가 생길수록 파드가 증가하고 안정 수준에 이르면 파드가 더이상 증가하지 않는다.

명령어 정리

# 디플로이먼트 생성 후 로드밸런서 서비스로 설정
$ kubectl create deployment aa --image=nginx
$ k expose deployment aa --type=LoadBalancer --name=aa-svc --port=80

# TYPE과 EXTERNAL-IP 부여 확인
$ k get svc

# 쿠버네티스 메트릭 서버(약간 변형) 설치
$ k apply -f _Book_k8sInfra/ch3/3.3.5/metrics-server.yaml

# 부하 확인 top(table of processes)
$ k top pods

# 아래 aa.yaml 참조해서 파드의 주어진 부하량 수정, m은 milliunits의 약어로 1000m이 CPU 1개 
$ k edit deployment aa

# 일정 시간 지난 후 스펙 변경 후 새로운 파드 생성 확인
$ k top pods

# autoscale(다른 노드로 넘기는) 조건 적용
$ k autoscale deployment aa --min=1 --max=30 --cpu-percent=50

# 마스터 노드 창을 하나 더 띄워서 2개에서 각각 watch
$ watch kubectl top pods
$ watch kubectl get pods

# 로컬에서 부하 테스트
$ brew install hey

$ hey -n 10000 -c 500 http://192.168.1.101

# -n : 요청 개수
# -c : 워커 개수 (요청 개수보다 적어야 한다)
# -z : 지속 시간

점점 파드 개수가 올라가다가 제한에 걸리고 요청이 없으면 점점 파드 개수가 줄어든다.

aa.yaml deployment request: CPU 0.01 사용하면 파드 증설 limit: CPU 0.05 사용하면 제한

resources:
  requests:
    cpu: "10m"
  limits:
    cpu: "50m"

3.4 알아두면 쓸모 있는 쿠버네티스 오브젝트

3.4.1 데몬셋

데몬셋은 노드 하나당 파드 하나만 생성한다. 단일 접속 지점으로 노드 외부와 통신하므로 파드가 1개 이상 필요하지 않다. 결국 노드를 관리하는 파드라면 데몬셋으로 만드는 게 효율적이다.

MetalLB의 스피커가 각 노드에 분포돼 있는 상태를 확인한다.

$ kubectl get pods -n metallb-system -o wide
NAME                          READY   STATUS    RESTARTS   AGE   IP              NODE     NOMINATED NODE   READINESS GATES
controller-5f98465b6b-fj4wj   1/1     Running   0          91m   172.16.132.18   w3-k8s   <none>           <none>
speaker-52lfb                 1/1     Running   0          91m   192.168.1.102   w2-k8s   <none>           <none>
speaker-j5l2j                 1/1     Running   0          91m   192.168.1.101   w1-k8s   <none>           <none>
speaker-xfwlt                 1/1     Running   0          91m   192.168.1.103   w3-k8s   <none>           <none>
speaker-xndlq                 1/1     Running   0          91m   192.168.1.10    m-k8s    <none>           <none>

워커 노드를 1개 늘린다. 5번째 줄에 N의 인자 값을 3에서 4로 수정

$ vim _Book_k8sInfra/ch3/3.1.3/Vagratfile

-w는 watch의 약어로 오브젝트 상태에 변화가 감지되면 해당 변화를 출력한다.

$ kubectl get pods -n metallb-system -o wide -w
$ kubectl get pods speaker-vn2k -o yaml -n metallb-system

3.4.2 컨피그맵

컨피그맵은 다른 오브젝트가 사용할 구성을 저장할 수 있는 API 오브젝트이다. spec 이 있는 대부분의 쿠버네티스 오브젝트와 달리, 컨피그맵에는 data 및 binaryData 필드가 있다. 이러한 필드는 키-값 쌍을 값으로 허용한다

컨피그맵은 설정을 목적으로 사용하는 오브젝트다. 인그레스에서는 설정을 위해 오브젝트를 인그레스로 선언했는데, MetalLB에서는 왜 컨피그맵을 사용했을까? 인그레스는 오브젝트가 인그레스로 지정돼 있지만, MetalLB는 프로젝트 타입으로 정해진 오브젝트가 없어서 범용 설정으로 사용되는 컨피그맵을 지정했다.

$ kubectl create deployment cfgmap --image=sysnet4admin/echo-hname
deployment.apps/cfgmap created
$ kubectl expose deployment cfgmap --type=LoadBalancer --name=cfgmap-svc --port=80
service/cfgmap-svc exposed
$ kubectl get services
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
cfgmap-svc   LoadBalancer   10.96.55.246   192.168.1.11   80:32602/TCP   29s
kubernetes   ClusterIP      10.96.0.1      <none>         443/TCP        28h

사전에 구성돼 있는 컨피그맵을 sed 명령을 사용해 변경한다.

$ cat ~/_Book_k8sInfra/ch3/3.4.2/metallb-l2config.yaml | grep 192.
- 192.168.1.11-192.168.1.13

$ sed -i 's/11/21/;s/13/23/' ~/_Book_k8sInfra/ch3/3.4.2/metallb-l2config.yaml

$ cat ~/_Book_k8sInfra/ch3/3.4.2/metallb-l2config.yaml | grep 192.
- 192.168.1.21-192.168.1.23

적용

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.4.2/metallb-l2config.yaml
configmap/config configured

MetalLB 관련 모든 파드를 삭제. 삭제하고 나면 kubelet에서 해당 파드를 자동으로 모두 다시 생성한다. --all은 파드를 모두 삭제하는 옵션이다.

$ kubectl delete pods --all -n metallb-system
pod "controller-5f98465b6b-fj4wj" deleted
pod "speaker-52lfb" deleted
pod "speaker-j5l2j" deleted
pod "speaker-w8bsc" deleted
pod "speaker-xfwlt" deleted
pod "speaker-xndlq" deleted

새로 생성된 MetalLB 파드들을 확인

$ kubectl get pods -n metallb-system
NAME                          READY   STATUS    RESTARTS   AGE
controller-5f98465b6b-vwcth   1/1     Running   0          39s
speaker-6ckrf                 1/1     Running   0          39s
speaker-7m7cr                 1/1     Running   0          39s
speaker-cmtjb                 1/1     Running   0          39s
speaker-k9v58                 1/1     Running   0          39s
speaker-r56gb                 1/1     Running   0          39s

기존에 노출한 MetalLB 서비스를 삭제하고 새로운 컨피그맵을 적용한 동일한 이름으로 다시 생성

$ kubectl delete service cfgmap-svc
service "cfgmap-svc" deleted

$ kubectl expose deployment cfgmap --type=LoadBalancer --name=cfgmap-svc  --port=80
service/cfgmap-svc exposed

바뀐 서비스의 IP가 192.168.1.21로 바뀌었는지 확인

$ kubectl get services
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
cfgmap-svc   LoadBalancer   10.96.60.182   192.168.1.21   80:31359/TCP   19s
kubernetes   ClusterIP      10.96.0.1      <none>         443/TCP        28h

컨피그맵 변경하려면 파일을 변경하고 변경한 파일을 적용한다. 기존 파드 중 바꾼 시스템 관련 파드들을 모두 삭제한다. 그러고 나서 서비스를 삭제하고 서비스를 다시 생성한다.

3.4.3 PV와 PVC

스토리지 관리는 컴퓨트 인스턴스 관리와는 별개의 문제다. 퍼시스턴트볼륨 서브시스템은 사용자 및 관리자에게 스토리지 사용 방법에서부터 스토리지가 제공되는 방법에 대한 세부 사항을 추상화하는 API를 제공한다. 이를 위해 퍼시스턴트볼륨 및 퍼시스턴트볼륨클레임이라는 두 가지 새로운 API 리소스를 소개한다.

퍼시스턴트볼륨 (PV)은 관리자가 프로비저닝하거나 스토리지 클래스를 사용하여 동적으로 프로비저닝한 클러스터의 스토리지이다. 노드가 클러스터 리소스인 것처럼 PV는 클러스터 리소스이다. PV는 Volumes와 같은 볼륨 플러그인이지만, PV를 사용하는 개별 파드와는 별개의 라이프사이클을 가진다. 이 API 오브젝트는 NFS, iSCSI 또는 클라우드 공급자별 스토리지 시스템 등 스토리지 구현에 대한 세부 정보를 담아낸다.

퍼시스턴트볼륨클레임 (PVC)은 사용자의 스토리지에 대한 요청이다. 파드와 비슷하다. 파드는 노드 리소스를 사용하고 PVC는 PV 리소스를 사용한다. 파드는 특정 수준의 리소스(CPU 및 메모리)를 요청할 수 있다. 클레임은 특정 크기 및 접근 모드를 요청할 수 있다(예: ReadWriteOnce, ReadOnlyMany 또는 ReadWriteMany로 마운트 할 수 있음. AccessModes 참고).

퍼시스턴트볼륨클레임을 사용하면 사용자가 추상화된 스토리지 리소스를 사용할 수 있지만, 다른 문제들 때문에 성능과 같은 다양한 속성을 가진 퍼시스턴트볼륨이 필요한 경우가 일반적이다. 클러스터 관리자는 사용자에게 해당 볼륨의 구현 방법에 대한 세부 정보를 제공하지 않고 크기와 접근 모드와는 다른 방식으로 다양한 퍼시스턴트볼륨을 제공할 수 있어야 한다. 이러한 요구에는 스토리지클래스 리소스가 있다.

파드는 언제라도 지워질 수 있지만 때때로 기록, 보관이 필요하다. 예를 들어 모든 파드가 동일한 설정 값을 유지, 관리하기 위해 공유 볼륨에서 공통 설정을 가져오는 것이다.

쿠버네티스는 파드의 기록, 보관이 필요할 때 PVC(PersistentVolumeClaim, 지속적으로 사용 가능한 볼륨 요청)를 요청해 사용한다. PVC를 사용하려면 PV(PersistentVolume, 지속적으로 사용 가능한 볼륨)로 볼륨을 선언해야 한다.


사용자가 퍼시스턴트볼륨클레임을 생성한 후에, 쿠버네티스 컨트롤 플레인은 클레임의 요구사항을 만족하는 퍼시스턴트볼륨을 찾는다. 컨트롤 플레인이 동일한 스토리지클래스를 갖는 적절한 퍼시스턴트볼륨을 찾으면, 볼륨에 클레임을 바인딩한다.

다음 단계는 볼륨으로 퍼시스턴트볼륨클레임을 사용하는 파드를 만드는 단계이다.

파드의 설정 파일은 퍼시스턴트볼륨클레임을 지정하고 퍼시스턴트볼륨을 지정하지는 않는다는 것을 유념하자. 파드의 관점에서 볼때, 클레임은 볼륨이다.


클러스터 관리자는 영속적인 스토리지를 만들기 위해 PV를 생성한다. PV는 동적으로 프로비저닝한 클러스터의 스토리지다. 파드 사용자는 PVC를 통해 PV에 접근을 요청한다.


What is the difference between a volume and a PV in Kubernetes? In summary, a volume is a way to provide storage for a single pod, while a PV is a way to provide shared, persistent storage for multiple pods.

NFS 볼륨에 PV/PVC를 만들고 파드에 연결하기

NFS 서버를 마스터 노드에 구성한다. 공유 디렉터리는 nfs_shared로 생성하고 해당 디렉터리를 NFS로 받아들일 IP 영역은 192.168.1.0/24로 정한다.

/nfs_shared 192.168.1.0/24(rw,sync,no_root_squash) 옵션을 /etc/exports에 저장한다. 옵션에서 rw는 읽기/쓰기, sync는 쓰기작업 동기화, no_root_sqush는 root 계정 사용을 의미한다. 이때 nfs-utils.x86_64는 현재 CentOS에 이미 설치돼 있다.

$ mkdir /nfs_shared
$ echo '/nfs_shared 192.168.1.0/24(rw,sync,no_root_squash)' >> /etc/exports

위 내용을 시스템에 적용해 NFS 서버를 활성화하고 다음에 서버를 시작할 때도 자동 적용되도록 systemctl enable --now nfs 명령을 실행한다.

$ systemctl enable --now nfs
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.

PV 생성

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.4.3/nfs-pv.yaml
persistentvolume/nfs-pv created

nfs-pv.yaml

apiVersion: v1  
kind: PersistentVolume  
metadata:  
  name: nfs-pv  
spec:  
  capacity:  # 1
    storage: 100Mi  # 1
  accessModes:  # 2
    - ReadWriteMany  # 2
  persistentVolumeReclaimPolicy: Retain  # 3
  nfs:  # 4
    server: 192.168.1.10  
    path: /nfs_shared
  • 1) 쓸 수 있는 양을 레이블로 붙이는 것과 같다. 용량을 제한하는 방법이 아니다.
  • 2) PV 사용방식 정의
    • ReadWriteMany는 여러 개의 노드가 읽고 쓸 수 있도록 마운트하는 옵션
    • ReadWriteOne(하나의 노드에서만 볼륨을 읽고 쓸 수 있게 마운트)
    • ReadOnlyMany(여러 개의 노드가 읽도록 마운트) 옵션이 있다.
  • 3) persistentVolumeReclaimPolicy는 PVC가 제거됐을 때 PV가 작동하는 방법을 정의하는 것이다. Retain 이외에 Delete가 있다.
  • 4) NFS 서버의 연결 위치에 대한 설정이다.

PV 상태가 Available인지 확인

$ kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
nfs-pv   100Mi      RWX            Retain           Available                                   57s

PVC 생성

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.4.3/nfs-pvc.yaml

nfs-pvc.yaml

apiVersion: v1  
kind: PersistentVolumeClaim  
metadata:  
  name: nfs-pvc  
spec:  
  accessModes:  
    - ReadWriteMany  
  resources:  
    requests:  
      storage: 10Mi

PVC와 PV는 구성이 거의 동일하지만 PV는 관리자가 사용자가 요청할 볼륨 공간을 만들고 PVC는 개발자가 사용자(개발자)간에 볼륨을 요청하는 데 사용한다는 점에서 차이가 있다.

상태가 Bound(묶여짐)으로 변경됐다. 이는 PV와 PVC가 연결됐음을 의미한다.

$ kubectl get pvc
NAME      STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
nfs-pvc   Bound    nfs-pv   100Mi      RWX                           7m24s

$ kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
nfs-pv   100Mi      RWX            Retain           Bound    default/nfs-pvc                           9m19s

PVC를 볼륨으로 사용하는 디플로이먼트 오브젝트 스펙을 배포

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.4.3/nfs-pvc-deploy.yaml
deployment.apps/nfs-pvc-deploy created

nfs-pvc-deploy.yaml

apiVersion: apps/v1  
kind: Deployment  
metadata:  
  name: nfs-pvc-deploy  
spec:  
  replicas: 4  
  selector:  
    matchLabels:  
      app: nfs-pvc-deploy  
  template:  
    metadata:  
      labels:  
        app: nfs-pvc-deploy  
    spec:  
      containers:  # 1
      - name: audit-trail   # 1
        image: sysnet4admin/audit-trail  # 1
        volumeMounts: # 2 
        - name: nfs-vol # 2 
          mountPath: /audit # 2  
      volumes:  # 3
      - name: nfs-vol  # 3
        persistentVolumeClaim:  # 3
          claimName: nfs-pvc  # 3
  • #1: 해당 컨테이너 이미지는 요청을 처리할 때마다 접속 정보를 로그로 기록
  • #2: 볼륨이 마운트될 위치를 지정
  • #3: PVC로 생성된 볼륨을 마운트하기 위해 nfs-pvc라는 이름을 사용
$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
cfgmap-8ddbf499-qhfcv             1/1     Running   1          15h
in-ip-pod-76bf6989d-sgt9x         1/1     Running   1          22h
nfs-pvc-deploy-5fd9876c46-28txz   1/1     Running   0          4m28s
nfs-pvc-deploy-5fd9876c46-m65r5   1/1     Running   0          4m28s
nfs-pvc-deploy-5fd9876c46-p44nc   1/1     Running   0          4m28s
nfs-pvc-deploy-5fd9876c46-w9vrt   1/1     Running   0          4m28s

생성한 파드 중 하나에 접속

$ kubectl exec -it nfs-pvc-deploy-5fd9876c46-28txz -- /bin/bash

PVC 마운트 상태 확인. 용량이 100Mi가 아니라 NFS 서버의 용량이 37G다.

$ df -h
Filesystem                   Size  Used Avail Use% Mounted on
overlay                       37G  3.2G   34G   9% /
tmpfs                        1.3G     0  1.3G   0% /dev
tmpfs                        1.3G     0  1.3G   0% /sys/fs/cgroup
192.168.1.10:/nfs_shared      37G  3.4G   34G  10% /audit
/dev/mapper/centos_k8s-root   37G  3.2G   34G   9% /etc/hosts
shm                           64M     0   64M   0% /dev/shm
tmpfs                        1.3G   12K  1.3G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs                        1.3G     0  1.3G   0% /proc/acpi
tmpfs                        1.3G     0  1.3G   0% /proc/scsi
tmpfs                        1.3G     0  1.3G   0% /sys/firmware

m-k8s 창을 하나 더 열고 audit-trail 컨테이너의 기능을 테스트한다. 외부에서 파드(nfs-pv-deploy)에 접속할 수 있도록 expose로 로드밸런서 서비스를 생성

$ kubectl expose deployment nfs-pvc-deploy --type=LoadBalancer --name=nfs-pvc-deploy-svc --port=80
service/nfs-pvc-deploy-svc exposed

생성한 로드밸런서 IP 확인

$ kubectl get services
NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
cfgmap-svc           LoadBalancer   10.96.60.182   192.168.1.21   80:31359/TCP   15h
kubernetes           ClusterIP      10.96.0.1      <none>         443/TCP        44h
nfs-pvc-deploy-svc   LoadBalancer   10.99.15.201   192.168.1.22   80:30514/TCP   18s

브라우저에서 192.168.1.22에 접속

접속 기록 파일이 남았는지 확인

$ ls /audit
audit_nfs-pvc-deploy-5fd9876c46-m65r5.log

$ cat /audit/audit_nfs-pvc-deploy-5fd9876c46-m65r5.log
10/Feb/2023:10:58:03 +0900  172.16.180.3  GET
10/Feb/2023:10:58:03 +0900  172.16.180.3  GET

마스터 노드(m-k8s)에서 파드를 8개로 늘린다.

$ kubectl scale deployment nfs-pvc-deploy --replicas=8

$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
cfgmap-8ddbf499-qhfcv             1/1     Running   1          15h
in-ip-pod-76bf6989d-sgt9x         1/1     Running   1          22h
nfs-pvc-deploy-5fd9876c46-28txz   1/1     Running   0          15m
nfs-pvc-deploy-5fd9876c46-cl6ln   1/1     Running   0          11s
nfs-pvc-deploy-5fd9876c46-j9c9l   1/1     Running   0          11s
nfs-pvc-deploy-5fd9876c46-lthdl   1/1     Running   0          11s
nfs-pvc-deploy-5fd9876c46-m65r5   1/1     Running   0          15m
nfs-pvc-deploy-5fd9876c46-p44nc   1/1     Running   0          15m
nfs-pvc-deploy-5fd9876c46-t6wwz   1/1     Running   0          11s
nfs-pvc-deploy-5fd9876c46-w9vrt   1/1     Running   0          15m

최근에 증가한 4개 파드 중 1개를 선택해서 audit 로그가 동일한지 확인

$ kubectl exec -it nfs-pvc-deploy-5fd9876c46-cl6ln -- /bin/bash


$ cat /audit/audit_nfs-pvc-deploy-5fd9876c46-m65r5.log
10/Feb/2023:10:58:03 +0900  172.16.180.3  GET
10/Feb/2023:10:58:03 +0900  172.16.180.3  GET

다른 브라우저에서 http://192.168.1.21/로 접속해 다른 파드 이름과 IP 표시되는지 확인

추가된 audit 로그 확인

$ ls /audit/
audit_nfs-pvc-deploy-5fd9876c46-lthdl.log  audit_nfs-pvc-deploy-5fd9876c46-m65r5.log

기존에 접속한 파드에서도 동일한 로그 목록이 있나 확인

$ ls /audit
audit_nfs-pvc-deploy-5fd9876c46-lthdl.log  audit_nfs-pvc-deploy-5fd9876c46-m65r5.log

NFS 볼륨을 파드에 직접 마운트하기

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.4.3/nfs-ip.yaml
deployment.apps/nfs-ip created

nfs-ip.yaml

apiVersion: apps/v1  
kind: Deployment  
metadata:  
  name: nfs-ip  
spec:  
  replicas: 4  
  selector:  
    matchLabels:  
      app: nfs-ip  
  template:  
    metadata:  
      labels:  
        app: nfs-ip  
    spec:  
      containers:  
      - name: audit-trail  
        image: sysnet4admin/audit-trail  
        volumeMounts:  
        - name: nfs-vol  
          mountPath: /audit  
      volumes:  
      - name: nfs-vol  # PV와 PVC를 거치지 않고 바로 NFS 서버로 접속
        nfs:  
          server: 192.168.1.10  
          path: /nfs_shared

새로 배포된 파드를 확인하고 그중 하나에 접속

$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
cfgmap-8ddbf499-qhfcv             1/1     Running   1          16h
in-ip-pod-76bf6989d-sgt9x         1/1     Running   1          23h
nfs-ip-7789f445b7-n554g           1/1     Running   0          2m55s
nfs-ip-7789f445b7-rs55j           1/1     Running   0          2m55s
nfs-ip-7789f445b7-tzjnf           1/1     Running   0          2m55s
nfs-ip-7789f445b7-xnh7z           1/1     Running   0          2m55s

$ kubectl exec -it nfs-ip-7789f445b7-n554g -- /bin/bash

접속한 파드도 동일한 NFS 볼륨을 바라보고 있음을 확인

$ ls /audit
audit_nfs-pvc-deploy-5fd9876c46-lthdl.log  audit_nfs-pvc-deploy-5fd9876c46-m65r5.log

볼륨 용량 제한하는 방법

PVC로 PV에 요청되는 용량을 제한
$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.4.3/limits-pvc.yaml
limitrange/storagelimits created
apiVersion: v1  
kind: LimitRange  
metadata:  
  name: storagelimits  
spec:  
  limits:  
  - type: PersistentVolumeClaim  
    max:  
      storage: 5Mi  
    min:  
      storage: 1Mi

용량제한 삭제

$ kubectl delete limitranges storagelimits
스토리지 리소스에 대한 총 용량 제한
$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.4.3/quota-pvc.yaml
resourcequota/storagequota created
apiVersion: v1  
kind: ResourceQuota  
metadata:  
  name: storagequota  
spec:  
  hard:  
    persistentvolumeclaims: "5"  
    requests.storage: "25Mi"
$ kubectl delete resourcequotas storagequota
resourcequota "storagequota" deleted

사용자가 퍼시스턴트볼륨클레임을 생성한 후에, 쿠버네티스 컨트롤 플레인은 클레임의 요구사항을 만족하는 퍼시스턴트볼륨을 찾는다. 컨트롤 플레인이 동일한 스토리지클래스를 갖는 적절한 퍼시스턴트볼륨을 찾으면, 볼륨에 클레임을 바인딩한다.

다음 단계는 볼륨으로 퍼시스턴트볼륨클레임을 사용하는 파드를 만드는 단계이다.

파드의 설정 파일은 퍼시스턴트볼륨클레임을 지정하고 퍼시스턴트볼륨을 지정하지는 않는다는 것을 유념하자. 파드의 관점에서 볼때, 클레임은 볼륨이다.


쿠버네티스에서 PV와 PVC가 묶여지는 기준은 무엇인가요?

쿠버네티스에서 PV와 PVC가 묶여지는 기준은 동일한 스토리지 클래스와 access mode를 가지고 있어야 합니다. PVC는 PV를 요청하는데 사용되며, PV는 클러스터 노드에서 스토리지로 마운트됩니다. PV와 PVC는 따로 생성될 수 있고, PVC가 생성될 때 PV와 바로 연결되지 않아도 됩니다. 대신, PVC는 동일한 스토리지 클래스와 access mode를 가지는 PV 중 하나와 연결됩니다. 이러한 방식으로 PV는 노드에서 스토리지를 추상화하고, PVC는 애플리케이션에서 필요로 하는 스토리지 요구 사항을 추상화합니다.

쿠버네티스에서 왜 PV와 PVC를 따로 만들었나요? 쿠버네티스에서 PV와 PVC를 따로 만든 이유는 스토리지의 추상화와 재사용을 가능케하기 위해서입니다. PV는 클러스터 노드에서 사용 가능한 스토리지를 추상화하며, PVC는 애플리케이션에서 필요로 하는 스토리지 요구 사항을 추상화합니다. 이를 통해 애플리케이션이 스토리지 클래스, access mode, 볼륨 용량 등과 같은 세부 사항을 직접 관리하지 않아도 되며, 클러스터 관리자는 PV를 프로비저닝하고 관리함으로써 스토리지 리소스를 재사용할 수 있습니다. 또한 PVC를 PV와 분리하여 독립적으로 만들면, 애플리케이션이 스토리지 요구 사항을 변경하거나 다른 스토리지 클래스를 사용하더라도, PVC를 재사용하여 쉽게 구성을 변경할 수 있습니다.

3.4.4 스테이트풀셋

스테이트풀셋을 언제 써야 하나? 파드가 만들어지는 이름과 순서를 예측해야 할 때, 주로 레디스와 같은 마스터-슬레이브 구조에서 필요하다.

스테이트풀셋은 애플리케이션의 스테이트풀을 관리하는데 사용하는 워크로드 API 오브젝트이다.

파드 집합의 디플로이먼트와 스케일링을 관리하며, 파드들의 순서 및 고유성을 보장한다 .

디플로이먼트와 유사하게, 스테이트풀셋은 동일한 컨테이너 스펙을 기반으로 둔 파드들을 관리한다. 디플로이먼트와는 다르게, 스테이트풀셋은 각 파드의 독자성을 유지한다. 이 파드들은 동일한 스펙으로 생성되었지만, 서로 교체는 불가능하다. 다시 말해, 각각은 재스케줄링 간에도 지속적으로 유지되는 식별자를 가진다.

스토리지 볼륨을 사용해서 워크로드에 지속성을 제공하려는 경우, 솔루션의 일부로 스테이트풀셋을 사용할 수 있다. 스테이트풀셋의 개별 파드는 장애에 취약하지만, 퍼시스턴트 파드 식별자는 기존 볼륨을 실패한 볼륨을 대체하는 새 파드에 더 쉽게 일치시킬 수 있다.

마스터-슬레이브 구조에서는 파드가 만들어지는 이름과 순서를 예측해야 할 때가 있는데 이 때 스테이트풀셋을 사용한다. 스테이트풀셋은 volumeClaimTemplates 기능을 사용해 PVC를 자동으로 생성할 수 있고 각 파드가 순서대로 생성되기 때문에 고정된 이름, 볼륨, 설정 등을 가질 수 있다.

그래서 StatefulSet(이전 상태를 기억하는 세트)이라는 이름을 사용한다. 다만 효율성 면에서 좋은 구조가 아니므로 적절히 사용해야 한다.

PV와 PVC는 앞서 이미 생성했으므로 바로 스테이트풀셋을 생성

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.4.4/nfs-pvc-sts.yaml 
statefulset.apps/nfs-pvc-sts created
apiVersion: apps/v1  
kind: StatefulSet  
metadata:  
  name: nfs-pvc-sts  
spec:  
  replicas: 4  
  serviceName: sts-svc-domain #statefulset need it  
  selector:  
    matchLabels:  
      app: nfs-pvc-sts  
  template:  
    metadata:  
      labels:  
        app: nfs-pvc-sts  
    spec:  
      containers:  
      - name: audit-trail  
        image: sysnet4admin/audit-trail  
        volumeMounts:  
        - name: nfs-vol # same name of volumes's name   
mountPath: /audit  
      volumes:  
      - name: nfs-vol  
        persistentVolumeClaim:  
          claimName: nfs-pvc

순서대로 생성이 되는지 확인

$ kubectl get pods -w
NAME                        READY   STATUS    RESTARTS   AGE
cfgmap-8ddbf499-qhfcv       1/1     Running   1          16h
in-ip-pod-76bf6989d-sgt9x   1/1     Running   1          23h
nfs-pvc-sts-0               1/1     Running   0          99s
nfs-pvc-sts-1               1/1     Running   0          95s
nfs-pvc-sts-2               1/1     Running   0          91s
nfs-pvc-sts-3               1/1     Running   0          87s
...

expose 명령은 스테이트풀셋을 지원하지 않는다.

$ kubectl expose statefulset nfs-pvc-sts --type=LoadBalancer --name=nfs-pvc-sts-svc --port=80
error: cannot expose a StatefulSet.apps
$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.4.4/nfs-pvc-sts-svc.yaml 
service/nfs-pvc-sts-svc created

$ kubectl get services
NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
kubernetes           ClusterIP      10.96.0.1      <none>         443/TCP        45h
nfs-pvc-sts-svc      LoadBalancer   10.102.45.8    192.168.1.23   80:32674/TCP   31s
apiVersion: v1  
kind: Service  
metadata:  
  name: nfs-pvc-sts-svc  
spec:  
  selector:  
    app: nfs-pvc-sts  
  ports:  
    - port: 80  
  type: LoadBalancer

브라우저에서 http://192.168.1.23/ 접속

exec로 파드에 접속한 후에 ls /audit -l로 접속한 파드 정보 추가됐는지 확인

$ kubectl exec -it nfs-pvc-sts-0 -- /bin/bash

$ ls -l /audit
-rw-r--r--. 1 root root 92 Feb 10 11:10 audit_nfs-pvc-deploy-5fd9876c46-lthdl.log
-rw-r--r--. 1 root root 92 Feb 10 10:58 audit_nfs-pvc-deploy-5fd9876c46-m65r5.log
-rw-r--r--. 1 root root 92 Feb 10 12:04 audit_nfs-pvc-sts-3.log

4장 도커

쿠버네티스 트러블 슈팅을 제대로 하려면 컨테이너를 잘 알아야 한다.

컨테이너는 하나의 운영체제 안에서 커널을 공유하며 개별적인 실행환경을 제공하는 격리된 공간이다.

4.2.1 컨테이너 이미지 알아보기

$ docker search nginx
INDEX       NAME                                                        DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
docker.io   docker.io/nginx                                             Official build of Nginx.                        18059     [OK]       
docker.io   docker.io/linuxserver/nginx                                 An Nginx container, brought to you by Linu...   184                  
docker.io   docker.io/bitnami/nginx                                     Bitnami nginx Docker Image                      150                  [OK]
docker.io   docker.io/ubuntu/nginx                                      Nginx, a high-performance reverse p
..

$ docker pull nginx
Using default tag: latest
Trying to pull repository docker.io/library/nginx ... 
latest: Pulling from docker.io/library/nginx
bb263680fed1: Pull complete # 레이어
258f176fd226: Pull complete 
a0bc35e70773: Pull complete 
077b9569ff86: Pull complete 
3082a16f3b61: Pull complete 
7e9b29976cce: Pull complete 
Digest: sha256:6650513efd1d27c1f8a5351cbd33edf85cc7e0d9d0fcb4ffb23d8fa89b601ba8
# 다이제스트
Status: Downloaded newer image for docker.io/nginx:latest
  • 다이제스트: 이미지 고유 식별자

4.2.2 컨테이너 실행하기

$ docker run -d --restart always nginx
  • -d(--detach): 백그라운드 구동
  • --restart always: 예상치 못한 오류 발생해 도커 서비스가 중지되는 경우 즉시 재시작하거나 시스템에서 도커 서비스가 작동할 때 컨테이너를 자동으로 시작한다.
$ docker ps
  • ps(process status)

컨테이너 검색(--filter)

$ docker ps -f id={16진수 ID}

filter 키로 id 이외에 name, label 등 가능

$ docker run -d -p 8080:80 --name nginx-exposed --restart always nginx
4928b4647ed43648238bb1e9c12dc8084341830feff1623e8022cf851399a03a
  • -p(--publish): 외부에서 호스트로 보낸 요청을 컨테이너 내부로 전달. 요청받을 호스트 포트:연결할 컨테이너 포트
  • --name: 이름 지정. 지정하지 않으면 임의로 설정된 이름 부여
$ docker ps -f name=nginx-exposed
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                  NAMES
4928b4647ed4        nginx               "/docker-entrypoin..."   About a minute ago   Up About a minute   0.0.0.0:8080->80/tcp   nginx-exposed

0.0.0.0은 존재하는 모든 네트워크 어댑터를 의미

0.0.0.0:8080->80/tcp: 0.0.0.0의 8080 포트로 들어오는 요청을 컨테이너 내부의 80번 포트로 전달한다.

4.2.3 컨테이너 내부 파일 변경하기

도커는 컨테이너 내부에서 외부의 파일을 사용할 수 있는 방법으로 크게 4가지를 제공한다.

  • docker cp
    $ docker cp <호스트 경로> <컨테이너 이름>:<컨테이너 내부 경로>
    

    호스트에 위치한 파일을 구동 중인 컨테이너 내부로 복사한다.

  • Dockerfile ADD Dockerfile에 ADD 구문으로 컨테이너 내부로 복사할 파일을 지정하면 이미지를 빌드할 때 지정한 파일이 이미지 내부로 복사된다.

  • 바인드 마운트 호스트의 파일 시스템과 컨테이너 내부를 연결해 어느 한쪽에서 작업한 내용이 양쪽에 동시 반영되는 방법이다. 새로운 컨테이너를 구동할 때도 호스트와 연결할 파일이나 디렉터리 경로만 지정하면 다른 컨테이너에 있는 파일을 새로 생성한 컨테이너와 연결할 수 있다. 데이터베이스의 데이터 디렉터리나 서버의 첨부 파일 디렉터리처럼 컨테이너가 바뀌어도 없어지면 안 되는 자료는 이 방법으로 보존할 수 있다.

  • 볼륨 호스트의 파일 시스템과 컨테이너 내부를 연결하는 것은 바인드 마운트와 동일하지만 호스트의 특정 디렉터리가 아닌 도커가 관리하는 볼륨을 컨테이너와 연결한다. 여기서 말하는 볼륨은 쿠버네티스에서 살펴본 볼륨 구조와 유사하다. 따라서 도커가 관리하는 볼륨 공간을 NFS와 같은 공유 디렉터리에 생성하면 다른 호스트에서도 도커가 관리하는 볼륨을 함께 사용할 수 있다.

바인드 마운트로 호스트와 컨테이너 연결하기

$ mkdir -p /root/html

컨테이너의 /usr/share/nginx/html/과 디렉터리와 호스트의 /root/html/ 디렉터리를 연결한다. -v(-volume)는 호스트 디렉터리와 컨테이너 디렉터리를 연결하는 옵션으로 호스트 디렉터리 경로:컨테이너 디렉터리 경로 형식으로 사용한다. 이때 앞서 사용한 8080번 포트와 중복되지 않게 8081번으로 지정해 컨테이너 내부의 80번포트와 연결한다. 여기서 중요한 바인드 마운트의 특성은 호스트 디렉터리의 내용을 그대로 컨테이너 디렉터리에 덮어쓴다는 점이다.

$ docker run -d -p 8081:80 -v /root/html:/usr/share/nginx/html --restart  always --name nginx-bind-mounts nginx
7a5a185a5df92b17fef0aa577895c427c8cde6959a4b39399780440fee231d71


-v <호스트 디렉토리>:<컨테이너 디렉토리>

컨테이너 상태가 정상(Up n minutes)인지 확인

$ docker ps -f name=nginx-bind-mounts
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
7a5a185a5df9        nginx               "/docker-entrypoin..."   53 seconds ago      Up 53 seconds       0.0.0.0:8081->80/tcp   nginx-bind-mounts

빈 디렉터리가 컨테이너와 연결

$ ls /root/html

http://192.168.1.10:8081/접속하면 403 Forbidden 응답(권한 에러 이외에 파일이 존재하지 않을 때도 403)

$ cp ~/_Book_k8sInfra/ch4/4.2.3/index-BindMount.html /root/html/index.htm

$ ls /root/html
index.html

http://192.168.1.10:8081/ 다시 접속

컨테이너 내부 확인

docker exec <컨테이너 ID 또는 이름> <명령어> 형식으로 실행하면 컨테이너에서 명령을 실행하고 결과를 보여준다.

$ docker exec 7a5 ls /usr/share/nginx/html
index.html

볼륨으로 호스트 컨테이너와 연결하기

볼륨은 도커가 직접 관리하며 컨테이너에 제공하는 호스트의 공간이다.

nginx-volume은 생성할 볼륨의 이름이다.

$ docker volume create nginx-volume
nginx-volume

생성된 볼륨을 조회

$ docker volume inspect nginx-volume
[
    {
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/nginx-volume/_data",
        "Name": "nginx-volume",
        "Options": {},
        "Scope": "local"
    }
]

Mountpoint가 볼륨 디렉터리다. 컨테이너 내부와 연결할 때 전체 디렉터리 경로를 사용하지 않고 nginx-volume이라는 볼륨 이름만으로 간편하게 연결할 수 있다.

볼륨으로 생성한 빈 디렉터리 확인

$ ls /var/lib/docker/volumes/nginx-volume/_data

옵션은 -v <볼륨 이름>:<컨테이너 디렉터리>이다. 컨테이너 내부의 /usr/share/nginx/html와 호스트의 nginx-volume을 연결하고 nginx-volume이라는 이름의 컨테이너를 구동한다.

$ docker run -d -v nginx-volume:/usr/share/nginx/html -p 8082:80 --restart always --name nginx-volume nginx
20d620f469986c68be2aae5be77a974e5dea031eaae8e66cd7753a363ad1850b

볼륨 디렉터리 다시 확인

$ ls /var/lib/docker/volumes/nginx-volume/_data
50x.html  index.html

바인드 마운트와 달리 볼륨은 빈 디렉터리를 덮어 쓰지 않고 파일을 보존한다. 양쪽을 서로 동기화시키는 구조다.

브라우저로 http://192.168.1.10:8082/ 접속

volume 디렉터리로 복사하면 컨테이너 디렉토리에 동기화되는지 테스트

$ cp ~/_Book_k8sInfra/ch4/4.2.3/index-Volume.html /var/lib/docker/volumes/nginx-volume/_data/index.html 
cp: overwrite ‘/var/lib/docker/volumes/nginx-volume/_data/index.html’? y

http://192.168.1.10:8082/ 접속

볼륨은 컨테이너에 존재하는 파일을 보존하고 변경 사용할 수 있어서 간편하다. 또한 docker volume ls, docker volume rm 명령도 사용 가능하다.

볼륨 경로를 바꾸려면 --data-root 옵션이나 호스트 경로와 컨테이너 경로를 연결하는 --mount옵션을 사용한다.

4.2.4 사용하지 않는 컨테이너 정리하기

$ docker ps -f ancestor=nginx
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
20d620f46998        nginx               "/docker-entrypoin..."   17 minutes ago      Up 17 minutes       0.0.0.0:8082->80/tcp   nginx-volume
7a5a185a5df9        nginx               "/docker-entrypoin..."   41 minutes ago      Up 41 minutes       0.0.0.0:8081->80/tcp   nginx-bind-mounts
4928b4647ed4        nginx               "/docker-entrypoin..."   2 hours ago         Up 2 hours          0.0.0.0:8080->80/tcp   nginx-exposed

-q(--quite) 옵션을 추가해 컨테이너 ID만 출력

$ docker ps -q -f ancestor=nginx

위 명령어를 인자로 넣어서 한꺼번에 정지

$ docker stop $(docker ps -q -f ancestor=nginx)
20d620f46998
7a5a185a5df9
4928b4647ed4

모든 컨테이너 정지됐는지 확인

$ docker ps -f ancestor=nginx
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

-a(--all) 옵션은 정지된 컨테이너를 포함해 생성한 컨테이너 모두 조회

$ docker ps -a -f ancestor=nginx
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
20d620f46998        nginx               "/docker-entrypoin..."   22 minutes ago      Exited (0) 2 minutes ago                       nginx-volume
7a5a185a5df9        nginx               "/docker-entrypoin..."   47 minutes ago      Exited (0) 2 minutes ago                       nginx-bind-mounts
4928b4647ed4        nginx               "/docker-entrypoin..."   2 hours ago         Exited (0) 2 minutes ago                       nginx-exposed

한꺼번에 컨테이너 삭제

$ docker rm $(docker ps -aq -f ancestor=nginx)
20d620f46998
7a5a185a5df9
4928b4647ed4

만약 'You cannot remove a running container' 오류가 나오면 컨테이너를 정지하지 않았거나 컨테이너 자동 재시작 옵션 때문인데 -f 옵션을 붙여서 삭제하면 된다.

한꺼번에 이미지 삭제

$ docker rmi $(docker images -q nginx)
Untagged: docker.io/nginx:stable
Untagged: docker.io/nginx@sha256:6b9d1c6e9826964d65710927416b526ec5939545e66ad42326ccb338880f2c5d
Deleted: sha256:7067317e38075c705a3917252a617eb2ab695cfe9b593161c757bb5a6e089e75
Deleted: sha256:e3eef3254dbcc9c657a752f4c43b2da77dd82de167aa2d14bed776001289ab14
Deleted: sha256:769de9204e32f9981dcb327f277304a7ca57f3e341fb26d2f07c7b5b6713ab3b
Deleted: sha256:497663399d18582513318dc012d42e9dc57f1c9ee24dfb98f3d8abb96d743223
Deleted: sha256:d0e926247b8861f16d9a056f2b0d619b458a3526eeb107f7e52657927978d274
Deleted: sha256:1bd14343fb475fa50de00822d4208f37522ee0eea7379cac4229f0ba6dd0ce0a
Untagged: docker.io/nginx:latest
Untagged: docker.io/nginx@sha256:6650513efd1d27c1f8a5351cbd33edf85cc7e0d9d0fcb4ffb23d8fa89b601ba8
Deleted: sha256:3f8a00f137a0d2c8a2163a09901e28e2471999fde4efc2f9570b91f1c30acf94
Deleted: sha256:ccfe545858415bccd69b8edff4da7344d782985f22ad4398bdaa7358d3388d15
Deleted: sha256:e34f63c02e162795cc8a2b43d1a3ff0ccd6d3456ce12aebb74452e252d1ecb8a
Deleted: sha256:cf7515030d4de4fb66994e0d9fccbaf19fcfbf46f7dad8cf895051750b840128
Deleted: sha256:1486739bc51436dd10d2bc1d45e130771c73d3aee35e49971905aa767d195342
Deleted: sha256:452008e5f3c114989bfc978a2829cf061f0868463f3553b4e20c964a41eda749
Deleted: sha256:4695cdfb426a05673a100e69d2fe9810d9ab2b3dd88ead97c6a3627246d83815

4.3 4가지 방법으로 컨테이너 이미지 빌드

4.3.1 기본 방법으로 빌드하기

$ cd ~/_Book_k8sInfra/ch4/4.3.1/

$ ls
Dockerfile  mvnw  pom.xml  src
$ yum install java-1.8.0-openjdk-devel -y
Loaded plugins: fastestmirror, product-id, search-disabled-repos, subscription-manager

This system is not registered with an entitlement server. You can use subscription-manager to register.

Loading mirror speeds from cached hostfile
epel/x86_64/metalink                          
...
$ chmod 700 mvnw
$ ./mvnw clean package
[INFO] Scanning for projects...
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/2.2.2.RELEASE/spring-boot-starter-parent-2.2.2.RELEASE.pom
Downloaded from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/2.2.2.RELEASE/spring-boot-starter-parent-2.2.2.RELEASE.pom (8.1 kB at 7.9 kB/s)
...
$ ls target
app-in-host.jar  app-in-host.jar.original  classes  generated-sources  maven-archiver  maven-status
$ docker build -t basic-img .
Sending build context to Docker daemon  17.7 MB
Step 1/6 : FROM openjdk:8
Trying to pull repository docker.io/library/openjdk ... 
8: Pulling from docker.io/library/openjdk
001c52e26ad5: Pull complete 
$ docker images basic-img
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
basic-img           latest              0bdeb93bdaae        29 seconds ago      544 MB
$ sed -i 's/Application/Development/' Dockerfile
$ docker build -t basic-img:3.0 .
Sending build context to Docker daemon  17.7 MB
Step 1/6 : FROM openjdk:8
 ---> b273004037cc
 ..
$ docker images basic-img
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
basic-img           3.0                 206eae332ee4        52 seconds ago      544 MB
basic-img           latest              0bdeb93bdaae        2 minutes ago       544 MB
$ docker run -d -p 60431:80 --name basic-run --restart always basic-img
3cf3ca3483e23473c3ce15eb6c2a5c164345304f1b11437c6bb31c8ba5c76b4c
$ curl 127.0.0.1:60431
src: 172.17.0.1 / dest: 127.0.0.1

목적지에 따라 출발지 표시가 다른 이유

컨테이너는 외부 요청이 목적지에 도착하기 전에 거친 네트워크 IP와 포트를 출발지(src)로 표시한다. 호스트 인터페이스(eh1)의 IP가 192.168.1.10이고 컨테이너 브리지 인터페이스(docker0)의 IP는 172.17.0.1이다.

eh1은 외부 요청을 받아들이는 네트워크 인터페이스이고 docker0은 도커 컨테이너가 사용하는 네트워크 인터페이스다. 따라서 도커 컨테이너가 외부와 통신하려면 docker0을 거쳐야 한다.

$ docker rm -f basic-run
basic-run

$ docker rmi -f $(docker images -q basic-img)
Untagged: basic-img:3.0
Deleted: sha256:206eae332ee41a3ff5cf0241843ca423eccd606159109783614752c9ad2b67a4
Deleted: sha256:87fbf7c10eaa80d06bd21e2753f7603b9a532c07068bcc19730ebf82c34e92d1
Deleted: sha256:1eca054b87a573f1e85daa16df5e3429d084d3e6008388cdf68d59697ec39ea6
Deleted: sha256:eadba4b7cff4f66484a84dc636200bb870ea731fd6e43a678ede15351d4b4bda
Deleted: sha256:9c1751e20f20621df91d47fd883afaecb66acbdc7b6eefe19027d153eb4bb8fb
Deleted: sha256:9ebc664f77af11662ec793a84ea99b83966e0a50076b281a66f43dc928f04df9
Untagged: basic-img:latest
Deleted: sha256:0bdeb93bdaae539ce84e62874b567fa9f0c6e3fb1a7ffdee53f7d225645c7ee8
Deleted: sha256:e13c449d903a02f87b2da5bca1b8ca27ea3e239ff541509dc7231785e042e599
Deleted: sha256:978f37fdd9e95dc5a4679f25b17af66525e6de0977bde78061912292812629ec
Deleted: sha256:113e20353b58cec0320b33ec1089845337855d8b1d35b01d16365772cc7e9525
Deleted: sha256:419e4cbfd4d5b18b3e88a3dbbb1aaabe7e00e1f9a9f692bdac97aee0ae646db0
Deleted: sha256:732bea31299a2222417cb651d145fc8e447857d3449323ef5124d145249dbbcc
$ docker build -t basic-img .
$ docker images basic-img

4.3.4 최적화해 컨테이너 빌드하기

멀티 스테이지 빌드

$ kubectl get nodes -o wide
NAME     STATUS   ROLES    AGE     VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION                CONTAINER-RUNTIME
m-k8s    Ready    master   12m     v1.18.4   192.168.1.10    <none>        CentOS Linux 7 (Core)   3.10.0-1127.19.1.el7.x86_64   docker://18.9.9
w1-k8s   Ready    <none>   9m7s    v1.18.4   192.168.1.101   <none>        CentOS Linux 7 (Core)   3.10.0-1127.19.1.el7.x86_64   docker://18.9.9
w2-k8s   Ready    <none>   5m49s   v1.18.4   192.168.1.102   <none>        CentOS Linux 7 (Core)   3.10.0-1127.19.1.el7.x86_64   docker://18.9.9
w3-k8s   Ready    <none>   3m1s    v1.18.4   192.168.1.103   <none>        CentOS Linux 7 (Core)   3.10.0-1127.19.1.el7.x86_64   docker://18.9.9
$ cd ~/_Book_k8sInfra/ch4/4.3.4
$ ls
Dockerfile  k8s-SingleMaster-18.9_9_w_auto-compl

멀티 스테이지의 핵심은 빌드하는 위치와 최종 이미지를 분리하는 것이다.

$ cat Dockerfile

## 기본

```shell
# 1단계: 자바 소스를 빌드에 JAR로 만듦
FROM openjdk:8 AS int-build  
LABEL description="Java Application builder"  
RUN git clone https://github.com/iac-source/inbuilder.git  
WORKDIR inbuilder  
RUN chmod 700 mvnw  
RUN ./mvnw clean package  

# 2단계: 빌드된 JAR를 경량화 이미지에 복사
FROM gcr.io/distroless/java:8  
LABEL description="Echo IP Java Application"  
EXPOSE 60434  
COPY --from=int-build inbuilder/target/app-in-host.jar /opt/app-in-image.jar  
WORKDIR /opt  
ENTRYPOINT [ "java", "-jar", "app-in-image.jar" ]
$ docker build -t multistage-img .
$ docker images | head -n 3
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
multistage-img                       latest              35ab7f1ff500        29 seconds ago      148MB
<none>                               <none>              6be4d4837873        44 seconds ago      615MB

이름이 없는 이미지를 댕글링(dangling) 이미지라고 한다. 멀티스테이지 과정에서 자바 소스를 빌드하는 과정에 생성된 이미지라고 보면 된다.

$ docker rmi $(docker images -f dangling=true -q)
Deleted: sha256:6be4d4837873d8d2e4bdb48d98633b2a1583a84302663acd87419f5ec02a36d7
Deleted: sha256:1106e283398b525199bd2bd144c18cac30e6238991ffee4c727ad74060ab76bd
Deleted: sha256:7a577bc2759358f31e23608cc532562c690eb5042707eb7b81e7eeb233e11483
Deleted: sha256:547160f5663df32c76a6188d43dd4249e8c7370e03f2af38a25ecf2b4b641d05
Deleted: sha256:9257affc960b9fb0cdb45413edf534b1a2c7fda017421ade12f4c4b3beb52e1a
Deleted: sha256:44ad3d33f19e1b02b9de12b8f97881e239602692adf93233293f3df7fb6f2464
Deleted: sha256:26454662f97606df15534921620df4b3bc019784d318a5076fd9d67d43a7c7c0
Deleted: sha256:bda165e67b5962af55b14a38301282083a20bdb6036910fc444f56e911b685fb
$ docker run -d -p 60434:80 --name multistage-run --restart always multistage-img
2ad896fd8fd72082286263bcbfd3497e08bc096a0083125ec06909971ae0f675
$ curl 127.0.0.1:60434
src: 172.17.0.1 / dest: 127.0.0.1

4.4 쿠버네티스에서 직접 만든 컨테이터 사용하기

4.4.1 쿠버네티스에서 도커 이미지 구동하기

내부에 존재하는 이미지로 deployment를 생성하면 ErrImagePull, ImagePullBackOff 오류가 나온다.

내부 컨테이너 이미지를 사용하도록 설정을 바꾸려면

  • --dry-run=client : 해당 내용을 실제로 적용하지 않은 채 명령을 수행하고
  • -o yaml: 은 현재 수행되는 명령을 야믈 형태로 바꾼다.
  • 두 옵션을 조합하면 현재 수행되는 명령을 실행하지 않고 야믈 형태로 출력해 사용자가 원하는 형태로 변경할 수 있다. 마지막에 > failure2.yaml을 붙여 실행 결과를 파일로 저장한다.
    $ kubectl create deployment failure2 --dry-run=client -o yaml --image=multistage-img > failure2.yaml
    

filure2.yaml을 열어 컨테이너 설정에 다음과 같이 추가한다.

spec:
  containers:
    - image: multistage-img
      imagePullPolicy: Never  # 추가한 부분
      name: multistage-img
      resources: {}
status: {}

그러고 나서 수정한 파일을 디플로이먼트에 적용해도 오류가 나온다. 하지만 마스터 노드가 아니라 워커 노드에 접속해서 다시 위 절차를 거치면 배포에 성공한다. 워커 코등에 이미지가 없어서 파드를 생성할 수 없기 때문이다. 이는 쿠버네티스 클러스터가 접근할 수 있는 레지스트리를 만들거나 도커 허브에 이미지를 올려서 해결할 수 있다.

4.4.2 레지스트리 구성하기

호스트에서 생성한 이미지를 쿠버네티스에서 사용하려면 모든 노드에서 공통으로 접근하는 레지스트리(저장소)가 필요하다.

도커에서 제공하는 도커 레지스트리 이미지를 사용해 사설 도커 레지스트리를 만들어보자. 도커 레지스트리는 기증은 부족하지만 설치가 간편해 내부 테스트 목적으로 적합하다.

$ ls ~/_Book_k8sInfra/ch4/4.4.2
create-registry.sh  remover.sh

tls.csr: 인증서를 만들 때 사용 create-registry.sh: 디렉터리에 인증서를 만들어 배포한 뒤 레지스트리 구동 remove.hs: 인증 문제가 생겼을 때 모든 설정을 지우는 스크립트

인증서를 생성하려면 서명 요청서(CSR, Certificate signing request)를 작성 해야 한다. 서명 요청서에는 몇 가지 추가 정보를 기록하고 이 서명 요청서를 기반으로 인증서와 개인키를 생성한다.

도커는 레지스트리 접속 과정에서 주체 대체 이름(SAN, Subject Alternative Name)이라는 추가 정보를 요청서에 기입해 검증한다.

tls.csr

[req]  
distinguished_name = private_registry_cert_req  
x509_extensions = v3_req  
prompt = no  
  
[private_registry_cert_req]  
C = KR  
ST = SEOUL  
L = SEOUL  
O = gilbut  
OU = Book_k8sInfra  
CN = 192.168.1.10  
  
[v3_req]  
keyUsage = keyEncipherment, dataEncipherment  
extendedKeyUsage = serverAuth  
subjectAltName = @alt_names  
  
[alt_names]  
DNS.0 = m-k8s  
IP.0 = 192.168.1.10

create-registry.sh: 레지스트리 생성하고 구동하는 과정이 담긴 스크립트. 인증서 생성과 배포, 레지스트리 생성과 구동의 순서다.

#!/usr/bin/env bash  
certs=/etc/docker/certs.d/192.168.1.10:8443  
mkdir /registry-image  
mkdir /etc/docker/certs  
mkdir -p $certs  
openssl req -x509 -config $(dirname "$0")/tls.csr -nodes -newkey rsa:4096 \  
-keyout tls.key -out tls.crt -days 365 -extensions v3_req  
  
yum install sshpass -y  
for i in {1..3}  
  do  
    sshpass -p vagrant ssh -o StrictHostKeyChecking=no root@192.168.1.10$i mkdir -p $certs  
    sshpass -p vagrant scp tls.crt 192.168.1.10$i:$certs  
  done  
  cp tls.crt $certs  
mv tls.* /etc/docker/certs  
  
docker run -d \  
  --restart=always \  
  --name registry \  
  -v /etc/docker/certs:/docker-in-certs:ro \  
  -v /registry-image:/var/lib/registry \  
  -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \  
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/docker-in-certs/tls.crt \  
  -e REGISTRY_HTTP_TLS_KEY=/docker-in-certs/tls.key \  
  -p 8443:443 \  
  registry:2
$ ~/_Book_k8sInfra/ch4/4.4.2/create-registry.sh
Generating a 4096 bit RSA private key
............................................................++
.......................++
writing new private key to 'tls.key'
-----
...
$ docker ps -f name=registry
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                             NAMES
0e66cf58a1e5        registry:2          "/entrypoint.sh /e..."   17 seconds ago      Up 16 seconds       5000/tcp, 0.0.0.0:8443->443/tcp   registry
$ curl -O https://raw.githubusercontent.com/sysnet4admin/_Book_k8sInfra/main/ch4/4.3.4/Dockerfile
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   421  100   421    0     0   1052      0 --:--:-- --:--:-- --:--:--  1055


$ docker build -t multistage-img .

이미지를 레지스트리에서 읽으려면 레지스트리 주소(IP와 도메인)와 이미지 이름을 레지스트리에 등록될 이름으로 지정해야 한다.

따라서 docker tag 명령으로 192.168.1.10:8443/multistage-img라는 multistage-img의 사본을 만든다. 이때 새로운 이미지를 만드는 것이 아니라 이미지의 레이어를 공유하는 사본이 만들어진다.

$ docker tag multistage-img 192.168.1.10:8443/multistage-img

확인

$ docker images 192.168.1.10:8443/multistage-img
REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
192.168.1.10:8443/multistage-img   latest              037b2e49cfdf        5 minutes ago       148M

사설 도커 레지스트리에 등록

$ docker push 192.168.1.10:8443/multistage-img
fae002fdc909: Pushed 
1d834f05c29e: Pushed 
b29380a5a354: Pushed 
231bdbae9aea: Pushed 
ba16d454860a: Pushed 
1a5ede0c966b: Pushed 
latest: digest: sha256:ade109a7f57423e27cd1a829732a206e777098bc36f9ddd78c083777095d6bd3 size: 1583

사설 도커 레지스트리는 curl <레지스트리 주소>/v2/_catalog로 요청을 보내면 레지스트리에 등록된 이미지의 목록을 보여준다. 자체 서명 인증서를 쓰는 사이트이기 때문에 -k(--insecure) 옵션으로 보안 검증을 생략하고 접속해야 한다.

$ curl https://192.168.1.10:8443/v2/_catalog -k
{"repositories":["multistage-img"]}

2개는 동일한 이미지다.

$ docker images | grep multi
192.168.1.10:8443/multistage-img     latest              037b2e49cfdf        8 minutes ago       148MB
multistage-img                       latest              037b2e49cfdf        8 minutes ago       148MB
$ docker rmi -f 037b2
Untagged: 192.168.1.10:8443/multistage-img:latest
Untagged: 192.168.1.10:8443/multistage-img@sha256:ade109a7f57423e27cd1a829732a206e777098bc36f9ddd78c083777095d6bd3
Untagged: multistage-img:latest
Deleted: sha256:037b2e49cfdf9897254e54a102dadb25c04ce59e38eb972ecc51402be02d112f
Deleted: sha256:87419782d261d16a24aa2e99ce13cb1251168b38f00843a9f1ffb893813dc7fb
Deleted: sha256:fbfa057e742a5f36acd39691948833293830b6bad9d34f3e92d2a5612be3b369
Deleted: sha256:395380c0fef977add903371b7c587a59694dfa439d1c6b319502b7500f0437f1
Deleted: sha256:841c233803c9cca7a6cdbb8ed33a3ee798edd9586de292ae65b6e2bcab5204d7
Deleted: sha256:89566fb763a47012bf1ef615772cbe2c5fcdc33504d01b6bbb3fbb8909b6939a

삭제됐는지 확인

$ docker images | grep multi

4.4.3 직접 만든 이미지로 컨테이너 구동하기

$ cd _Book_k8sInfra/ch4/4.3.4

$ docker build -t multistage-img .
Sending build context to Docker daemon  11.26kB
Step 1/12 : FROM openjdk:8 AS int-build
 ---> b273004037cc
...
$ kubectl create deploy failure1 --image=multistage-img
deployment.apps/failure1 created

$ kubectl get po -w
NAME                        READY   STATUS         RESTARTS   AGE
failure1-6dc55db9d4-2q8lw   0/1     ErrImagePull   0          14s
failure1-6dc55db9d4-2q8lw   0/1     ImagePullBackOff   0          16s
failure1-6dc55db9d4-2q8lw   0/1     ErrImagePull       0          30s
$ kubectl create deploy failure2 --dry-run=client -o yaml --image=multistage-img > failure2.yaml


$ vim failue2.yaml
    spec:
      containers:
      - image: 192.168.1.10:8443/multistage-img  ##
        name: multistage-img
        resources: {}
$ k get po -o wide
NAME                        READY   STATUS             RESTARTS   AGE     IP               NODE     NOMINATED NODE   READINESS GATES
failure1-6dc55db9d4-2q8lw   0/1     ImagePullBackOff   0          4m28s   172.16.103.129   w2-k8s   <none>           <none>
failure2-6c476c6bcd-m5lbp   1/1     Running            0          25s     172.16.132.1     w3-k8s   <none>  
$ curl  172.16.132.1 
src: 172.16.171.64 / dest: 172.16.132.1

Source