[Infra] 쿠버네티스(kubernetes)(3) 쿠버네티스로 nginx, flask 연동 후 배포하기

업데이트:

쿠버네티스 서비스 배포하기(nginx, flask 연동)

참고 링크

1. 쿠버네티스 기초

쿠버네티스는 sudo 권한으로 실행해야합니다. 사용자 권한으로 kubectl 같은 명령어 내리면 안되요. 따라서 코드를 치기전에 반드시 다음 커맨드를 입력해 sudo 로 전환합시다.

# sudo -i

1-1. 파드(pod)란? 일시적인 존재

파드는 하나의 가상 서버라고 생각하며 편합니다. 이때 그냥 일반적인 가상 서버가 아니라 일시적인 서버라고 생각하는 것입니다. (일시적이라는 말은 영구적이지 않다는 뜻) 따라서 파드를 늘린다는 것은 서버를 늘린다고 생각하면 됩니다.

파드(pod)란 쿠버네티스에서 컨테이너를 실행하는 최소 단위입니다. 파드에는 컨테이너가 포함되어 있는데, 이 때, 하나의 파드에는 컨테이너 하나가 포함될 수도 있고, 여러 개의 컨테이너가 포함될 수도 있습니다.

그리고 하나의 파드에 속하는 컨테이너들은 같은 노드에서 동작합니다. 즉, 동일한 파드에 속해 있는 컨테이너들이 서로 다른 노드에 흩어진 상태로 동작할수는 없다는 뜻입니다. (하나의 노드에 뭉쳐있어야함)

또한 파드 내부의 컨테이너들은 파드의 IP 주소와 포트번호를 공유합니다. 그리고 파드 내부 컨테이너들은 localhost로 서로 통신할 수 있으며, 파드의 볼륨을 마운트하여 파일 시스템을 공유할 수 있습니다.

파드 IP는 기동시 부여되고 종료시 회수되어 다른 파드가 사용할 수 있습니다. 즉, 파드는 실행할 때마다 IP가 변경되므로 파드에 요청을 보내고 싶을 떄는 반드시 서비스(service)를 사용해야합니다.

파드는 하나의 가상 서버 처럼 동작합니다.

파드는 단독으로 사용되기 보다는 서비스, 컨트롤러, 컨피그맵, 퍼시스턴트 볼륨, 시크릿 등의 오브젝트와 함께 사용됩니다.

1-2. 서비스(service)란?

쿠버네티스의 서비스(service)는 클라이언트의 요청을 파드에 전달하는 역할을 담당합니다. 서비스가 필요한 이유는 파드의 IP가 계속 변경되기 때문입니다. 따라서 파드에 접속하고자 하는 클라이언트는 파드에 바로 붙는 것이 아니라 서비스의 대표 IP를 통해서 파드 접속해야합니다. 이때 클라이언트는 외부의 클라이언트일수도 있고 다른 파드가 될 수도 있습니다.

서비스는 로드밸런서 역할을 하며 클라이언트의 요청을 받기 위해 대표 IP 주소(clusterIP)를 획득합니다. 그리고 서비스의 이름은 내부 DNS에 등록되기 때문에 클라이언트는 서비스의 이름만으로 서비스의 IP주소를 획득할 수 있습니다. 클라이언트에게 받은 요청에 대해 서비스는 실렉터(selector)에 지정된 라벨과 일치하는 파드 중 하나에게 요청을 전달합니다. 서비스가 만들어지고 기동된 파드의 컨테이너에는 서비스에 대한 정보가 담긴 환경 변수가 자동으로 설정됩니다. 서비스의 종류에는 4가지 종류가 있는데, 클라이언트의 범위를 쿠버네티스 클러스터 내부로 한정할 지, 외부로 확장할지, 혹은 클러스터 외부의 IP 주소에 전송할지를 설정합니다.

1-3. 컨트롤러(controller)란?

컨트롤러는 파드를 제어합니다. 파드에게 부여할 워크로드 타입을 선택해야하는데 워크로드 타입 종류는 프론트엔드 처리, 백엔드 처리, 배치 처리, 시스템 운영 처리가 있습니다. 컨트롤러의 역할은 디플로이먼트(deployment), 스테이트풀셋(statefulset), 잡(job), 크론잡(cronjob), 데몬셋(daemonset), 레플리카셋(replicaset), 레플리케이션 컨트롤러(replication controller)가 있습니다. 가장 자주 사용되는 컨트롤러는 디플로이먼트 입니다. 디플로이먼트는 백엔드 역할을 하는데, 디플로이먼트의 주된 역할은 파드의 개수를 관리하는 것입니다.

2. Hello world!

2-1. Hello world 도커 이미지 실행하기

도커에 hello-world 이미지를 쿠버네티스의 파드로 실행하려면 다음과 같이 실행합니다. kubectl run 명령어를 사용할 때는 옵션은 –restart=Always 옵션을 사용하면 디플로이먼트 컨트롤러(이하 디플로이먼트)의 제어하에서 실행할 수 있습니다. 반면 –restart=Never 옵션을 주면 파드만을 독립적으로 실행할 수 있습니다.

# kubectl run hello-world --image=hello-world --restatrt=Never

그리고 파드의 로그를 보려면 다음과 같이 입력합니다.

# kubectl logs hello-world

2-2. 쿠버네티스 클러스터 정보

다음 커맨드를 입력해 쿠버네티스가 돌아가는 클러스터 정보를 확인합시다.

# kubectl cluster-info
Kubernetes control plane is running at https://10.1.55.220:6443
CoreDNS is running at https://10.1.55.220:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

위 정보를 보면 클러스터 정보가 나타납니다.

2-3. 쿠버네티스 노드 확인

다음 커맨드를 입력하면 쿠버네티스 노드를 확인할 수 있습니다.

# kubectl get node
NAME    STATUS   ROLES                  AGE   VERSION
brain   Ready    control-plane,master   21d   v1.23.4

결과를 보면 노드가 하나인 것을 알 수 있습니다. 저는 서버가 한 대밖에 없어서 노드가 하나 입니다.

2-4. 파드, 서비스 확인

파드(pod) 확인은 다음과 같이 입럭합니다. 아래 결과에서 아무 결과가 안나오는 것은 현재 운영중인 파드가 없기 때문입니다.

# kubectl get pod
No resources found in default namespace.

서비스(service) 확인은 다음과 같이 입력합니다.

# kubectl get service
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   21d

3. 디플로이먼트와 서비스 배포하기

3-0. 폴더 구조

실습에 쓰일 폴더 구조는 다음과 같습니다.

위 그림에서 deployment.yml과 service.yml 파일을 제외한 나머지 파일은 도커를 이용해서 이미 만들어 놓은 파일입니다. 따라서 이번 실습에서 작성할 파일은 deployment.yml, service.yml 뿐입니다.

3-1. 배포할 때 필요한 것

(이 파일은 현재 작성중입니다.)

도커 이미지 파일이 필요합니다. 지금까지 찾아본 바로는 쿠버네티스 yaml 파일로 이미지 빌드까지는 안되는거 같고 도커로 이미지를 빌드해서 이미지가 준비된 상태에서 쿠버네티스로 배포만 합니다.

yaml(‘야믈’이라고 읽음) 파일 작성

deployment.yml
service.yml

그리고 이전에 만들었던 nginx 설정 변경을 위해 nginx 폴더 내부에 있는 default.conf 파일을 다음과 같이 바꿔줍니다. 이전에는 upstream 파트가 있었는데 이번에는 upstream을 없애고 proxy_pass 부분에 바로 이동할 주소를 적어주었습니다.

default.conf 
server{
        listen 8004;
        server_name 10.1.55.220;

        location /{
        proxy_read_timeout 3000;
        proxy_connect_timeout 3000;
        proxy_send_timeout 3000;
        send_timeout 3000;
        proxy_pass http://127.0.0.1:5004;
        }

}

3-2. YAML 파일 작성하기

쿠버네티스에서 파드를 가동시킬때는 메니페스트(manifest)를 작성하는데 매니페스트란 쿠버네티스의 오브젝트 생성을 위한 메타정보를 YAML이나 JSON으로 기술한 파일을 의미합니다. YAML이란 YAML Aint’t Markup Language의 약자로 마크업언어가 아니라는 뜻을 가집니다. YAML파일은 아래와 같이 작성하는데 하이픈(-) 다음에 오는 요소는 배열에서 하나의 요소가 됨을 의미합니다.

메니페스트 파일을 작성할 때 파드만을 위해 단독으로 작성되는 경우는 거의 없고 보통 컨트롤러에 대한 메니페스트를 작성하면서 파드에 대한 정보가 포함되도록 작성하는 경우가 많습니다. YAML 파일을 실행하는 방법은 ‘kubectl apply -f ' 입니다. 만약 지우고 싶다면 'kubectl delete -f ' 입니다.

참고로 yaml 파일 작성할 때 주의해야할 점은 들여쓰기 할때, tab 쓰면 안되고 space 써야합니다.

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx-lipaco
          image: living_paradise_nginx
          imagePullPolicy: Never
          ports:
            - containerPort: 8004
        - name: flask-lipaco
          image: living_paradise_predict_product_demand
          imagePullPolicy: Never
          ports:
            - containerPort: 5004

deployment.yml 파일을 보면 컨테이너가 두개 존재하는 것을 볼 수 있습니다. 그리고 imagePullPolicy: Never의 의미는 해당 이미지를 넷상에서 pull 받는게 아니라 로컬에 있는 도커 이미지를 사용하겠다는 것을 의미합니다.

위 그림과 같이 파드 안에는 nginx-lipaco 컨테이너와 flask-lipaco 컨테이너가 존재합니다. 다음으로 service.yml 파일은 다음과 같이 작성합니다.

serivce.yml
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8004
      nodePort: 30004  

위 코드에서 nodePort는 30000 부터 32767 사이의 숫자로 정해야 합니다. 위 두 파일의 관계는 다음과 같습니다.

그리고 이를 그림으로 표현하면 다음과 같습니다.

그리고 두개의 파일을 보면 포트가 많은데 port, nodePort, targetPort, containerPort가 있습니다. 이들의 관계를 그림으로 나타내면 다음과 같습니다.

3-3. 배포

$ sudo -i
# kubectl apply -f deployment.yml
# kubectl apply -f service.yml

3-4. 배포 잘됬는지 확인

# kubectl get pod
NAME                          READY   STATUS    RESTARTS   AGE
web-deploy-69c4bd9c68-2b24s   2/2     Running   0          5m37s
web-deploy-69c4bd9c68-gl86k   2/2     Running   0          5m37s
web-deploy-69c4bd9c68-lmcvb   2/2     Running   0          5m37s
# kubectl get service
NAME          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes    ClusterIP      10.96.0.1       <none>        443/TCP        23d
web-service   LoadBalancer   10.101.155.68   <pending>     80:30004/TCP   109s

위 코드에서 kubectl get service 명령어를 쳤을 때 EXTERNAL-IP가 PENDING인 이유는 아이피를 따로 설정하지 않았기 때문입니다.

3-5. 특정 파드의 컨테이너 로그 확인

# kubectl logs po/web-deploy-6757866546-5fvsh -c nginx-lipaco
Error from server (BadRequest): container "nginx-lipaco" in pod "web-deploy-6757866546-5fvsh" is waiting to start: trying and failing to pull image

3-10. 삭제

# kubectl delete -f deployment.yml
# kubectl delete -f service.yml

3-11. 동작중인 특정 파드의 특정 컨테이너 내부로 접근하기

$ kubectl exec -it web-deploy-69c4bd9c68-2b24s --container flask-lipaco -- /bin/bash