쿠버네티스(Kubernetes) 알아보기

https://www.samsungsds.com/kr/insights/220222_kubernetes1.html

 

 

쿠버네티스란?

역사

 오늘날의 어플리케이션은 하나의 통일체의 형태로 동작하는 것이 아니라, 하나의 어플리케이션의 동작을 위해서 필요한 수많은 컨테이너들이 느슨하게 결합되어 함께 작동하는 형태로 구성되어 있다. MSA환경을 기반으로 만들어진 어플리케이션이 여러 개의 서버로 구성되어 있고, 서비스가 독립적으로 개발되고 배포되는 구조로 이루어져 있다는 것을 생각해보면 이해가 쉽다.

 

위의 그림은 전통적인 배포 환경과, 가상 환경을 이용한 배포, 그리고 컨테이너를 사용한 배포의 차이를 보여준다. 전동적인 배포는 물리적인 컴퓨터 하나와 하나의 운영체제 위에 여러개의 프로그램을 설치하는 방식이었다. 한대의 컴퓨터에서 여러 프로그램을 실행하다 보니 애플리케이션의 개수가 늘어날수록 퍼포먼스 저하와 프로그램 충돌이 일어나는 등의 문제가 나타났다.

 

이러한 문제를 해결하기 위해서 여러개의 물리적 컴퓨터를 이용하는 원시적인 방법도 있지만, 하나의 시스템 위에 여러개의 가상 컴퓨터(VM)를 올려 구동하는 방법이 있다. 각각의 VM 논리적 컴퓨터이므로, CPU 메모리를 각각 할당할 있다. 이렇게 하면 가상머신 각각의 성능을 조절할 있으며 프로그램 충돌을 방지할 있지만, 무거운 VM 여러개를 결국 하나의 하드웨어가 감당해야 하기에 성능적인 한계가 나타나게 된다.

 

 그래서 나온 방법이 여러개의 VM 올리는 대신 '컨테이너 런타임'이라는 것을 사용하여, 모든 애플리케이션이 하나의 OS 공유하지만 각각의 컨테이너 사이에는 간섭을 일으키지 않도록 독립적인 자원을 할당하고 관리해주는 방법이며, 이러한 컨테이너 런타임을 관리하기 위한 툴이 쿠버네티스이다. 참고로 도커가 대표적인 컨테이너 런타임이며, 그래서 도커랑 엮여서 많이 사용되며 언급되는 것이다.

 

 정리하면 앱이 구동되는 환경 전반을 감싸서 실행될 있도록 하는 격리기술을 컨테이너라고 하는데, 그러한 컨테이너를 다루기 위한 도구를 컨테이너 런타임이라고 한다(가장 유명한 컨테이너 런타임이 도커(Docker)이다 - k8s는 컨테이너 런타임 인터페이스(CLI)라는 프로토콜을 사용하여 컨테이너와 통신을 진행한다). 그리고 이러한 수많은 컨테이너들을 모두 독립적으로 관리하며 배포하는 일은 쉽지 않기에 컨테이너들을 전반적으로 관리하는 컨테이너 오케스트레이션이라는 기술을 사용하게 되는데, 쿠버네티스는 이러한 컨테이너 오케스트레이션을 통해서 컨테이너를 다루기 위한 툴이다 라고 정리할 수 있겠다.

 

k8s(컨테이너 오케스트레이션)을 이용했을 때의 장점

- 서비스 디스커버리와 로드 밸런싱
- 스토리지 오케스트레이션
- 롤아웃, 롤백 자동화
- 자동화된 빈 패킹
- 자동화된 복구
- 시크릿, 구성 관리

 

 

 

kubectl

 k8s를 사용하다 보면 kubectl이라는 명령어를 자주 이용하게 된다. 이것은 Kubernetes API 서버와 유저의 통신을 중개하는 역할을 한다. 사용자의 명령어가 입력되면 그것을 바탕으로 API서버에 요청을 전송하고, 응답을 다시 사용자에게 전송한다.

 

예를 들어 'kubectl exec -it [파드명] -- redis-cli' 와 같이 파드 내의 redis를 실행했다고 하자. 그럼 대략적으로 다음과 같은 과정을 거치게 된다.

1. K8s API서버로 파드 내의 redis-cli를 실행하라는 요청을 보낸다.
2. k8s API서버는 이를 받아 처리하고, 파드가 실행중인 노드로 요청을 전달한다.
3. 각 노드에 있는 에이전트인 Kubelet은 요청을 받아서 명령어를 실행한다.
4. 실행 결과는 다시 API서버와 kubectl을 차례대로 거쳐 사용자에게 출력된다. 

 

 

 

쿠버네티스 오브젝트

 쿠버네티스 오브젝트란 서비스, 파드와 같이 영속성을 갖는, '의도성을 가지는 레코드'라고 할 수 있다. 오브젝트를 생성하면 쿠버네티스 시스템은 해당 오브젝트에 의도한 상태를 유지하기 위해 작동하게 되는데, 이 '의도한 상태'를 yml문서에 담아 나타낼 수 있다.

 

이 오브젝트를 조작하기 위한 모든 요청은 쿠버네티스 API 호출을 통해서 이루어지며, 일반적으로 이러한 정보들은 .yml파일의 형태로 저장된다. kubectl은 yml 형태의 파일을 json으로 변환하여 쿠버네티스 API로 요청하게 된다.

 

 

 

 

vs Docker-Compose

 그러면 쿠버네티스와 docker-compose는 무슨 차이를 가질까? 도커 컴포즈도 docker-compose.yml파일에 미리 여러 컨테이너를 정의하고 한번에 순차 실행시키고, 둘 다 comfiguration yaml 파일을 통해서 관리한다.

 

비슷해 보이지만 docker-compose는 단순히 도커상에서 여러개의 컨테이너를 한번에 관리하기 위한 편의성을 위한 방법이고, 쿠버네티스는 도커보다 많은 컨테이너를 관리하는 컨테이너 오케스트레이션을 위해서 필요한 기능이다. 간단하게 말하면 도커 컴포즈는 쿠버네티스 없이 도커만 사용할경우 반복작업을 줄이고 편의성을 늘리기 위해서 사용하는 방법 정도라고 이해하면 될 듯 하다.

 

 

 

개념 정리하기

Pod

 쿠버네티스는 컨테이너를 직접 실행하지 않고, 컨테이너(이미지)를 파드(Pod)라는 단위에 감싸서 실행한다.

파드는 컨테이너가 1 이상 캡슐화되어 저장공간과 네트워크 공유하는 객체 말한다. 파드는 클러스터에서 관리하는 배포의 최소 단위이기 때문에, 파드에 속한 컨테이너를 어떻게 구동할지에 대한 명세를 갖는다. 컨테이너와 다르게, 파드는 구동 상태를 유지하기 위해서 사용되는 일회성 자원이며, 필요에 따라서 언제든 삭제되고 생성될 있다.

 

 하나의 파드는 하나의 컨테이너를 포함(Wrapper)하는 것이 일반적이지만, 같은 생명주기를 공유하고 함께 붙어있어야 하는 컨테이너들이 존재한다면 여러개의 컨테이너를 하나의 파드로 묶어 관리할  있다. 따라서 위에서 1개의 스프링 부트 프로젝트와 3개의 database 컨테이너 각각을 별도의 파드로 관리할 수도 있다.

 

이러한 파드는 후술할 워커 노드에서 실행된다. 즉, 워커 노드는 파드를 실행하는 일종의 가상 머신이 된다. 하지만 동일한 종류의 파드라고 해서 동일한 워커 노드 안에서 실행되지는 않는다. 동일한 Deployment에서 생성된 동일한 종류의 파드들이 서로 다른 워커노드에서 실행될 수 있다. 이렇게 흩어진 노드들은 Control-plane노드의 컨트롤러 매니저나 스케줄러에 의해서 삭제, 생성될 수 있다.

 

이렇게 직접 생성하는 경우는 드물다

 파드는 사용자에 의해 직접 생성되지는 않고, Deployment, StatefulSet, Daemonset과 같은 상위 수준의 워크로드 리소스로 파드를 관리한다. 이는 파드가 일회용 불변 객체로 설계되었으며, 파드를 노드에 스케줄링하던 중 문제가 생겼다면 기존 파드를 삭제하고 재생성하는것이 더 바람직하기 때문이다.

 

파드는 크게 다음과 같은 라이프사이클(상태)을 가진다.

의미
Pending 클러스터에서 승인되었지만, 컨테이너가 설정되지 않음
Running 파드가 노드에 바인딩되었고, 컨테이너가 모두 생성됨
Succeeded 파드의 모든 컨테이너가 성공적으로 종료됨
Failed 파드의 하나 이상의 컨테이너가 실패로 종료됨
Unknown 파드의 상태를 얻을 수 없음

 

 

Cluster

 클러스터는 애플리케이션 컨테이너를 실행하기 위한 일련의 노드들로 이루어진 머신들의 집합으로, 여러개의 머신 또는 노드가 함께 작동하여 컨테이너화된 애플리케이션을 배포하고 관리한다. 클러스터 안에 존재하는 모든 파드들은 k8s의 마스터 노드에 의해서 워커 노드에 적절하게 배포되어 실행된다.

 

Node

 

 노드는 클러스터 내에서 애플리케이션을 실행하고, 쿠버네티스 오브젝트를 관리하는 머신 말한다. 이러한 노드는 가상머신(VM)일수도, 물리적 머신(온프레미스)일수도 있다. 하나의 클러스터 안에는 여러개의 노드가 존재하며, 노드는 클러스터 내의 파드를 실행하고 관리한다.

후술하겠지만 쿠버네티스 환경 전반에 대한 컨트롤을 하기 위한 scheduler controller manager등의 마스터 노드와, 파드에 대한 컨트롤을 하기 위한 kubelet kube proxy 같은 워커 노드들이 존재한다.

 

정리하면 클러스터는 노드들의 집합이고, 노드 안에서는 파드가 실행되며, 파드 안에는 한 개 이상의 컨테이너가 실행되고, 해당 컨테이너 안에서 애플리케이션이 실행된다.

 

Context

 클러스터와 노드가 물리적인 구성 단위라면, 컨텍스트와 네임스페이스는 논리적인 구성 단위라고 있다. 

컨텍스트는 연결 대상이 되는 클러스터, 네임스페이스, 그리고 유저를 묶어놓은 구성 단위를 말한다. 사용하는 컨텍스트에 따라서 대상이 되는 클러스터와 유저, 네임스페이스를 다르게 관리할 있다.

 

NameSpace

 네임스페이스는 쿠버네티스 오브젝트들을 논리적으로 묶은 구성 단위이다. 인그레스, 서비스, 파드와 같은 오브젝트들을 유사한 목적을 가진 묶음으로 grouping해서 네임스페이스라는 단위로 관리할  있다. 

https://artist-developer.tistory.com/33

 

 네임스페이스는 왜 사용할까? 대표적인 예시는 위처럼 배포 환경을 기준으로 namespace를 분리한다면, 실사용자가 많은 production 환경에는 많은 리소스를 할당하고, 테스트를 위한 staging이나 develop 환경에는 적은 리소스를 할당하여 리소스를 효과적으로 사용할 수 있다.

 

 

Controller

 컨트롤러는 클러스터의 상태를 관찰한 다음, 파드를 관리하여 클러스터의 상태를 의도에 근접하게 이동시키는 역할을 한다. 사용자가 관리하는 k8s 객체를 워크로드 리소스라고 하는데, 워크로드 리소스의 작업을 도우며 의도된 상태를 유지하도록 하는 프로세스를 컨트롤러라고 한다.

 

대표적으로는 다음과 같은 워크로드 리소스가 있다. 각 워크로드 리소스마다 관리의 범위가 다른 것을 확인할 수 있다.

- 상태를 유지하지 않아도 되는 파드를 관리하는 경우
-> Replication Controller
-> ReplicaSet
-> Deployment

- 클러스터 전체에 배포가 필요한 파드를 관리하는 경우
-> Daemon Set

- 상태관리가 필요한 파드를 관리하는 경우
-> Stateful Set

- 배치성 작업을 진행하는 파드를 관리하는 경우
-> Cronjob

 

 

Deployment, ReplicaSet

 애플리케이션의 배포와 업데이트를 관리하는 리소스로, Pod 생성을 비롯한 라이프사이클을 관리한다. 기존 ReplicaSet과 RepliactionController의 기능을 포함하면서 추가적인 기능을 제공한다. Deployment는 롤링 업데이트, 롤백, 스케일링과 같은 애플리케이션의 라이프사이클 관리를 지원하며, 이를 통해서 안정적으로 업데이트를 수행하거나 롤백할 수 있다. 대표적인 옵션으로 ReplicaSet이 있는데, 이것은 파드의 복제본(Replica)들을 관리하는 컨트롤러의 역할을 한다.

 

 후술하겠지만 이러한 ReplicaSet은 파드의 인스턴스 개수를 일정 개수 이상으로 유지하도록 하여 파드의 안정성을 보장한다. (이중화를 통해 한 서버에 장애가 발생했을 때 failover가 가능하게끔 한다)

 

Deployment 예시

 

내부에 1개의 컨테이너를 가진 파드 하나를 생성하는 Deployment의 예시이다. 

apiVersion: apps/v1
kind: Deployment # 생성하려는 k8s 리소스 유형
metadata:
  name: my-deployment # 사용할 리소스명
spec: # deployment 구성에 대한 세부 내용
  replicas: 1 # 시작 시 생성할 실행 파드의 개수
  selector: # 라벨을 통해 제어할 파드 선택
    matchLabels:
      tier: one # tier가 one인 모든 파드 제어
  template: # deployment에서 생성되어야 할 파드 정보
    kind: Pod
    metadata:
      labels: # 생성할 라벨. selector.matchLabel을 통해 연결 가능
        tier: one
    spec: # deployment 내부 개별 pod의 사양
      containters: 
        - name: first-node
          image: eckrin/kube-first-app:2 # 추가하고 싶은 컨테이너 이미지
kubectl apply -f==deployment.yml

 

yml 파일을 바탕으로 kubectl을 사용하여 deployment를 적용할 수 있다.

 

 

Service

 파드 내의 컨테이너끼리는 localhost 접근이 가능하지만, 외부에서 파드에 접근하기 위해서는 서비스를 정의해주어야 한. 서

서비스란 '파드 그룹을 네트워크 서비스로 노출하는 추상화 방법'을 말하는데,  Pod를 논리적으로 구분하여 특정 IP를 통해 접근하도록 하는 것이다. 쿠버네티스는 ClusterIp, External Name, NodePort, LoadBalancer 등의 서비스를 제공한다.

 

서비스 또한 파드와 동일하게 yml 파일을 사용하여 세팅해줄  있다. 

 

 

 위의 yml 밑에서 쿠버네티스 실습을 할 때 작성한 것인데, 이걸로 예를 들어 설명하겠다.

 

 kind필드를 Service 지정해주어 서비스임을 명시하고, metadata name 서비스 이름을 지정해준다. 다음 spec 필드 맡에 연결한 파드의 네트워크와 관련된 설정들을 하면 되는데, 위와 같이 설정하면 spring-kubernetes라는 라벨을 가진 파드를 대상으로, 외부에서 TCP 연결을 통해 30001 포트로 오는 요청들을 받아서 내부 8080 포트번호를 갖는 파드로 요청을 전달해준다. (port 필드의 경우 내부에서 서비스를 사용하기 위한 포트이다.)

 

  위 yml에는 타입이 'nodePort' 되어있다는 특징이 있는데 nodePort 경우 클러스터 내의 노드에 외부로 노출되는 포트를 할당하여 외부에서 IP 포트를 통해서 서비스에 직접 접근할 있지만, ClusterIP와 같은 경우는 클러스터 내부에서만 접근이 가능하다는 차이가 있다.

 

 사실 쿠버네티스에서 서비스는 개발 테스트 환경에서만 외부로 IP 개방하고, 실제 서비스에서는 클라이언트에서는 접근이 불가능하도록 생성한다. 따라서 클라이언트에서 접근할 있는 외부 ip port 제공하고, 외부 클라이언트와 쿠버네티스 서비스를 연결해 무언가가 필요한데, 그것이 서비스에 대한 로드 밸런서인 ingress이다.

 

 

Ingress

 

 

 아까 말했듯 상용 서비스 환경에서는 서비스를 통해 외부로 직접 개방하는 경우는 많지 않으며, 클라이언트에서 접근가능한 외부 주소와 쿠버네티스 서비스를 연결해주는 로드 밸런서를 두는 경우가 일반적이다. 이는 서비스를 외부에서 노출시킬 있는 자체도 제한이 있을 아니라, 다수의 백엔드 서비스가 존재할 경우 트래픽을 분산시키거나, 복잡한 네트워크 정책을 관리하거나 라우팅할 필요가 있는 여러가지 기능을 제공하기 때문이다.

 이러한 인그레스 설정을 위해서는 인그레스 컨트롤러가 필요한데, 사용하려는 인그레스 컨트롤러의 옵션에 따라서 yml파일을 설정해주고, 인그레스 리소스를 정의해준 클러스터에 적용하면 된다. 대표적인 인그레스 컨트롤러는 nginx, traefik, HAProxy와 같은 것들이 있는데, 일반적으로 인그레스 컨트롤러는 제공하는 yml의 private ip정도만 커스텀하고, 인그레스 리소스 파일을 수정해주면 된다.

 

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  namespace: my-namespace
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: my-domain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app1-service
                port:
                  number: 80
  - host: my-domain2.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app2-service
                port:
                  number: 80

 

 

 my-namespace라는 namespace 내에서만 이 인그레스에 접근할 수 있으며, spec.rules 밑에 정의된 2개의 호스트에 대해서 라우팅을 진행하는 간단한 인그레스 yml파일의 예시이다. 이 인그레스는 my-domain.com으로 들어오는 요청은 app1-service로, my-domain2.com으로 들어오는 요청은 app2-service로 라우팅해준다.

 

 

Volume

 컨테이너와는 독립적이지만, Pod에 종속된 저장소를 말한다. 파드에 종속된 만큼 설정값에 따라서 파드의 생명주기에 따라서 저장소의 데이터가 손실되는 등의 특징이 있다. 이러한 종속성을 없애기 위해서는 PV와 PVC를 사용할 수 있다.

 

 

 

PV, PVC

 

 파드 자체는 영속성을 보장하지 않기 때문에 파드도 파드 자신의 생명주기와 무관하게 저장되는 데이터 저장소가 필요한데, 이를 위한 것이 PV PVC이. PV 데이터 저장소 자체를 의미하고, 클러스터에서 사용 가능한 스토리지 자체를 나타내며, PVC 사용자가 PV 하는 요청에 대한 인터페이스를 말한다. 

 

 이것들은 그대로 데이터의 영속성을 보장해줄 있는 저장장치, 볼륨을 의미한다. , 이때 쿠버네티스 볼륨을 파드에 직접 할당하는 것이 아니라, 중간에 PVC(Claim) 두고 PV 파드 사이에 PVC 바인딩해서 파드가 사용할 저장공간과 파드를 분리했다. 이러한 구조는 파드 각각의 상태와 상황에 맞게 다양한 스토리지를 활용할 있게 한다.

 

PV 만드는 단계를 프로비저닝이라고 하는데, 이미 정의된 PV 사용하는 정적 프로비저닝과, 요청시마다 PV 생성하는 동적 프로비저닝이 존재한다. 정적 프로비저닝은 사용 가능한 스토리지 용량에 제한이 있는 경우 유용하고, 동적 프로비저닝은 사용자가 원하는 용량만큼을 생성해서 사용하는 방법으로 PVC 거쳐서 PV 요청했을 생성된다. 

 

 

 

 

k8s Architecture

 

https://www.opsramp.com/guides/why-kubernetes/kubernetes-architecture/

 

<Control-Plane Nodes>

  1. 가장 먼저 쿠버네티스 클러스터로 들어오는 모든 REST 요청들은 API 서버에서 받아서 유효성을 검증한 다음, 해당 요청을 처리할 적절한 컴포넌트에게 전달된다. 사용자의 요청을 받으면 일반적으로 API 서버는 Authentication 통해서 요청에 대한 인증 정보를 파악하고, RBAC Authorization 통해서 접근 허용 여부를 결정하고, Admission Control 통해서 인증과 인가에 대한 필요한 변경을 진행한다.
  2. 두번째로는 컨트롤러 매니저가 있는데, 클러스터를 구성하는 4가지 리소스 객체를 통제하는 컴포넌트이다. 노드가 다운되었을때 조치하거나, 네임스페이스의 계정과 인증 토큰을 생성하고, 서비스 생성시 적절한 리소스를 생성하고, 파드의 개수를 통제하는 노드를 전체적으로 제어하는 역할을 한다.
  3. 다음으로는 파드를 어떤 노드에 생성할지를 결정하는 스케줄러가 있다. 노드의 리소스 현황이나 yml 설정과 같은 요소들을 고려하여 어떤 노드에 생성할지를 결정하게 된.
  4. 마지막으로 쿠버네티스 오브젝트의 상태 정보를 저장하는 etcd 있다. etcd 쿠버네티스 오브젝트의 모든 정보가 저장되어 있기 때문에 쿠버네티스 클러스터 백업시 etcd 백업해주면 된다.

 

<Worker Nodes>

워커 노드는 CPU와 메모리를 가지는 일종의 컴퓨터라고 할 수 있다. 파드는 쿠버네티스에 의해서 사용 가능한 워커 노드로 배포되어, 그 안에서 파드가 실행되며, 외부 통신을 통해서 파드를 관리할 수 있다.

 

  1. kubelet 워커노드에 실행된 컨테이너가 동작하도록 관리하는 컴포넌트로, 자신의 노드에 실행된 컨테이너가 정상적으로 동작하도록 모니터링하고 관리하는 역할을 한.
  2. 다음으로는 kube-proxy 있다. 이것은 네트워크 프록시 컴포넌트라고 부르는데, 클라이언트와 서버간의 통신을 중개하거나 제어하여 여러가지 기능을 하는데 사용된다. 쉽게 말하면 외부의 서비스 오브젝트와 파드를 연결하는 역할을 하고, 파드 간의 네트워크 트래픽을 관리한다.
  3. 마지막으로 컨테이너 런타임(Docker)이다. 처음에 쿠버네티스에 역사에 대해 설명할 때, 모든 애플리케이션이 하나의 OS 공유하지만 각각의 컨테이너 사이에는 간섭을 일으키지 않도록 독립적인 자원을 할당하고 관리하기 위해 사용하는 것이 컨테이너 런타임이라고 말했었는데, 쿠버네티스는 각 워커노드마다 존재하는 컨테이너 런타임을 사용하여 kubelet과 상호작용하여 컨테이너를 관리한다.

 

 

특징

Owner-Dependents

 k8s의 모든 오브젝트는 Owner 아니면 Dependent 관계로 연결되어 있다(예를 들면, Replicaset은 파드 집합들의 owner라고 할 수 있다). 따라서 k8은 이를 이용하여 여러 가지 기능을 지원한다. 오브젝트를 삭제할 때, 종속 오브젝트들을 자동으로 삭제하도록 cascading deletion을 지원하기도 하고, kubelet이 사용되지 않는 이미지나 컨테이너에 대한 GC를 진행하기도 한다(이미지는 5min, 컨테이너는 1min).

 

 

 

쿠버네티스 실습해보기

k8s configuration file 세팅하기

 쿠버네티스에서 지원하는 여러가지 기능들을 컨트롤하기 위해서는 yml 또는 json으로 구성된 configuration file 정의해야 한. 디렉토리 아래 .kube 디렉토리에 configuration 파일을 만들어주면 된.

 

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: ~~
    server: https://aks-d-aop-dns-pue1y1ro.hcp.koreacentral.azmk8s.io:443
  name: AKS-D-AOP
contexts:
- context:
    cluster: AKS-D-AOP
    user: clusterUser_RG-AOP-DEV_AKS-D-AOP
  name: AKS-D-AOP
current-context: AKS-D-AOP
kind: Config
preferences: {}
users:
- name: clusterUser_RG-AOP-DEV_AKS-D-AOP
  user:
    client-certificate-data: ~~
    client-key-data ~~
    token: ~~

 

 크게 cluster, user, context의 세 파트로 나누어서 보면 된다.

 

현재 AKS-D-AOP라는 이름을 갖는 클러스터(cluster) 하나를 정의할 때, certificate-authority-data 클러스터에 대한 CA 인증서를 나타내는데, 클러스터와 사용자 간의 안전한 통신을 위한 인증서와 관련된 정보를 포함하고 있다. server 접근하려는 애저 쿠버네티스 서버의 url 나타낸다.

 밑에 있는 users 필드를 통해서는 clusterUser_RG-AOP-DEV_AKS-D-AOP라는 유저를 정의하고, 사용자의 클라이언트 인증서와 키를 Base64 인코딩한 데이터를 정의했다.

 마지막으로 contexts 필드는 클러스터와 사용자를 결합하여 특정 작업을 수행하는데 사용되는 환경을 정의하는데, 먼저 정의한 AKS-D-AOP라는 클러스터와, 밑에서 정의한 clusterUser 결합하여 AKS-D-AOP라는 콘텍스트를 만들었다.

 

실습 1. pod 사용해서 컨테이너 실행시키기

간단하게 spring 프로젝트와 mysql, redis, mongodb 컨트롤러를 만들어주고, 쿠버네티스를 사용하여 연결해보자. docker-compose 사용시와 동일하게 쿠버네티스 사용시에도 configuration 설정을 위해 yaml파일을 작성해주어야 한다.

 

apiVersion: v1
kind: Pod
metadata: # Pod명과 라벨(오브젝트 분류를 위함) 지정
  name: web
  labels:
    app: spring-kubernetes
spec: # Pod에서 실행될 컨테이너의 스펙(이미지, CPU, 메모리 등)을 지정
  containers:
    - name: mysql-db
      image: mysql
      ports:
        - containerPort: 3306
      env:
        - name: MYSQL_DATABASE
          value: "mysql-database-name"
        - name: MYSQL_ROOT_HOST
          value: '%'
        - name: MYSQL_ROOT_PASSWORD
          value: "mysql-database-root-password"

    - name: redis-db
      image: redis
      ports:
        - containerPort: 6379

    - name: mongo-db
      image: mongo
      ports:
        - containerPort: 27017
      env:
        - name: MONGO_INITDB_ROOT_USERNAME
          value: "mongo-root-username"
        - name: MONGO_INITDB_ROOT_PASSWORD
          value: "mongo-root-password"
        - name: MONGO_INITDB_DATABASE
          value: "mongo-root-database"

    - name: spring-docker
      image: spring-docker-spring-boot
      ports:
        - containerPort: 8080
      env:
        - name: MYSQL_URL
          value: "jdbc:mysql://host.docker.internal:3306/mysql-database-name?serverTimezone=Asia/Seoul"
        - name: MYSQL_USERNAME
          value: "mysql-database-username"
        - name: MYSQL_PASSWORD
          value: "mysql-database-password"
        - name: MONGO_HOST
          value: "host.docker.internal"
        - name: MONGO_PORT
          value: "27017"
        - name: MONGO_URI
          value: "mongodb:/mongo-username:mongo-password@host.docker.internal:27017/docker_test?authSource=admin"
        - name: REDIS_HOST
          value: "host.docker.internal"
        - name: REDIS_PORT
          value: "6379"

 

$ kubectl apply -f <yml 파일경로>
pod/web created

$ kubectl get pods
$ kubectl get all

$ kubectl describe pod <pod-name>

$ kubectl delete pod <pod-name>

 

그런데 spring 컨테이너는 실행되지 않고 

Failed to pull image "docker.io/library/spring-docker-spring-boot": rpc error: code = Unknown desc = Error response from daemon: pull access denied for spring-docker-spring-boot, repository does not exist or may require 'docker login': denied: requested access to the resource is denied

이런 오류가 발생하였다.

kubectl create secret docker-registry docker-hub-secret --docker-server=https://index.docker.io/v1/ --docker-username=YOUR_DOCKERHUB_USERNAME --docker-password=YOUR_DOCKERHUB_PASSWORD --docker-email=YOUR_DOCKERHUB_EMAIL
apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    app: spring-kubernetes
spec:
  containers:
    - name: spring-docker
      image: docker.io/library/spring-docker-spring-boot
      ports:
        - containerPort: 8080
      env:
        # 환경 변수 설정
      imagePullSecrets:  # Docker Hub Secret 지정
        - name: docker-hub-secret

 

- docker pull 

 

1. secret 추가하고 imagePullSecrets를 추가해주기

2. docker login해보기

-> docker login panic assignment to entry in nil map.. 오류가 떠서 로그아웃후 로그인 해주었더니 해결

$ docker logout
$ docker login

하지만 그래도 해결되지 않았다.

 

3. docker hub에 푸시하고 yml파일에 이미지 경로를 docker.io/<사용자명>/<image명>과 같이 지정해주어 허브에서 갖고오게 한다. push하기 전에 이미지명을 바꾸어주어야 한다.

$ docker image tag <image명> <docker유저명>/<image명>
$ docker push <docker유저명>/<image명>

 

 

 

 

 

 

 

실습 2. service 사용해서 만들어진 pod에 접근하기

 

 하지만 이렇게 생성된 Pod는 외부에서 직접 접근할 수 없으며, 외부에서 접근하기 위해서는 서비스를 사용해야 한다. 쿠버네티스에서 각 Pod들은 고유한 IP주소를 가지는데, 이 IP는 클러스터 내부에서만 유효하다. 보안적인 문제도 있고, Pod는 수시로 생성/삭제되기기에 동적으로 IP가 할당되기에 외부에서 직접 접근하기 쉽지 않다. 따라서 Service 리소스를 생성하여 생성한 Pod와 연결한 뒤, 스프링부트 컨테이너에 접근해보도록 하겠다.

 

 먼저 Service 리소스를 생성하여 Pod와 연결해보도록 하자. 이렇게 하면 서비스는 Pod의 ip주소와 port를 고정시켜 외부에서 그것을 사용하여 접근할 수 있게 된다.

 

apiVersion: v1
kind: Service
metadata:
  name: web-service
  labels:
    app: spring-kubernetes-service
spec:
  selector:
    app: spring-kubernetes # Pod의 라벨과 일치
  type: NodePort
  ports:
    - port: 80 # 서비스 사용 포트
      targetPort: 8080 # 내부 pod로 연결할 포트
      nodePort: 30001 # 외부에 노출할 포트
      protocol: TCP
      name: http

 

위와 같이 service.yml을 만들고, Pod를 만들때와 동일하게 apply 명령어를 사용하여 실행시켜준다.

 

$ kubectl apply -f <service명>

$ kubectl get services <service명>

$ kubectl get all

 

 

kubectl get all 명령어를 통해서  조회해보면 서비스가 생성된 것을 확인할 수 있고, 생성된 포트를 통해 접속하면 스프링부트에 접근 가능하다.

 

 

 

 

 

 

실습 3. secret 사용해서 민감한 정보 숨기기

 

 

 쿠버네티스에서는 환경변수를 사용하여 Pod에 secret 정보를 주입할 수 있다. 비슷한 기능으로 ConfigMap이 있지만 이는 민감한 정보를 다루는데 사용하는 것을 권장하지 않는다.

 

secret을 사용하기 위해서는 아래와 같이 kubectl을 사용하여 직접 secret을 만들 수도 있지만

$ kubectl create secret ...

 

secret이 복잡해질 경우를 대비해 yml을 사용하였다.

 

먼저 docker-hub-secret이라는 secret을 생성했다.

 

# secret.yml
apiVersion: v1
kind: Secret
metadata:
  name: docker-hub-secret
type: Opaque
data:
  MONGO_INITDB_ROOT_USERNAME: ENCODED_MONGO_INITDB_ROOT_USERNAME
  MONGO_INITDB_ROOT_PASSWORD: ENCODED_MONGO_INITDB_ROOT_PASSWORD
  MONGO_INITDB_DATABASE: ENCODED_MONGO_INITDB_DATABASE
  ...

 

$ kubectl apply -f secret.yml
$ kubectl get secrets # check working

그다음 secret을 등록해주고,

 

# pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    app: spring-kubernetes
spec:
  containers:
    - name: mongo-db
      image: mongo
      ports:
        - containerPort: 27017
      envFrom:
        - secretRef:
            name: docker-hub-secret

  imagePullSecrets: # Docker Hub Secret 지정
    - name: docker-hub-secret

위와 같이 Pod를 수정해주면 된다. 기존에는 pod.yml의 env: 아래에 줄줄이 평문으로 환경변수들을 노출시켰으나, 이제는 등록한 secret을 명시해주기만 하면 끝

 

 

 

실습 4. 볼륨 적용해보기

 

 

 

 

 

 

 

기타 궁금한 점

 

1. Pod 생성시 registry.k8s.io/pause:3.9라는 의도하지 않는 정체불명의 컨테이너가 생성됨.

 

자동 생성되는 컨테이너라고 한다.

 

 

2. kubectl get all로 조회해보면 service/kubernetes라는 서비스가 기본으로 생성되어 있는 것을 볼 수 있는데,

 

역시 1번과 같이 자동 생성되며, 위와 같은 역할을 하는 서비스이다.

 

 

3. kubectl apply -f <pod명>으로 pod를 실행시키면, pod 안에 있는 스프링부트 컨테이너가 1번 죽었다가 다시 새로 실행해서 동작함

 

이것도 docker-compose 사용했을때처럼 실행시점 문제 같아서 initcontainer 관련 설정 찾아보자

 

 

4. NodePort를 사용했을때 ip주소가 localhost임. (LoadBalancer Ingress 부분)

 

$ kubectl describe service <service명>

 

찾아보니 로컬 개발환경에서는 localhost로 지정할 수 있고, 아니면 서비스 configuration yml에 externalIp를 설정해주면 변경 가능한 것 같다.