블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 

'실습'에 해당되는 글 2

  1. 2018.06.18 쿠버네티스 #7 - 서비스 (Service) (5)
  2. 2018.06.17 쿠버네티스 #6 - 실제 서비스 배포해보기 (3)
 

쿠버네티스 #7

서비스 (service)


조대협 (http://bcho.tistory.com)


Service

쿠버네티스 서비스에 대해서 자세하게 살펴보도록 한다.

Pod의 경우에 지정되는 Ip가 랜덤하게 지정이 되고 리스타트 때마다 변하기 때문에 고정된 엔드포인트로 호출이 어렵다, 또한 여러 Pod에 같은 애플리케이션을 운용할 경우 이 Pod 간의 로드밸런싱을 지원해줘야 하는데, 서비스가 이러한 역할을 한다.

서비스는 지정된 IP로 생성이 가능하고, 여러 Pod를 묶어서 로드 밸런싱이 가능하며, 고유한 DNS 이름을 가질 수 있다.


서비스는 다음과 같이 구성이 가능하며, 라벨 셀렉터 (label selector)를 이용하여, 관리하고자 하는 Pod 들을 정의할 수 있다.


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer

멀티 포트 지원

서비스는 동시에 하나의 포트 뿐 아니라 여러개의 포트를 동시에 지원할 수 있다. 예를 들어 웹서버의 HTTP와 HTTPS 포트가 대표적인 예인데,  아래와 같이 ports 부분에 두개의 포트 정보를 정의해주면 된다.

apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

   - name: https

     port: 443

     protocol: TCP

     targetPort: 8082

 type: LoadBalancer

로드 밸런싱 알고리즘

서비스가 Pod들에 부하를 분산할때 디폴트 알고리즘은 Pod 간에 랜덤으로 부하를 분산하도록 한다.

만약에 특정 클라이언트가 특정 Pod로 지속적으로 연결이 되게 하려면  Session Affinity를 사용하면 되는데, 서비스의 spec 부분에 sessionAffinity: ClientIP로 주면 된다.




웹에서 HTTP Session을 사용하는 경우와 같이 각 서버에 각 클라이언트의 상태정보가 저장되어 있는 경우에 유용하게 사용할 수 있다.

Service Type

서비스는 IP 주소 할당 방식과 연동 서비스등에 따라 크게 4가지로 구별할 수 있다.

  • Cluster IP

  • Load Balancer

  • Node IP

  • External name


ClusterIP

디폴트 설정으로, 서비스에 클러스터 IP (내부 IP)를 할당한다. 쿠버네티스 클러스터 내에서는 이 서비스에 접근이 가능하지만, 클러스터 외부에서는 외부 IP 를 할당  받지 못했기 때문에, 접근이 불가능하다.

Load Balancer

보통 클라우드 벤더에서 제공하는 설정 방식으로, 외부 IP 를 가지고 있는 로드밸런서를 할당한다. 외부 IP를 가지고 있기  때문에, 클러스터 외부에서 접근이 가능하다.

NodePort

클러스터 IP로만 접근이 가능한것이 아니라, 모든 노드의 IP와 포트를 통해서도 접근이 가능하게 된다. 예를 들어 아래와 같이 hello-node-svc 라는 서비스를 NodePort 타입으로 선언을 하고, nodePort를 30036으로 설정하면, 아래 설정에 따라 클러스터 IP의  80포트로도 접근이 가능하지만, 모든 노드의 30036 포트로도 서비스를 접근할 수 있다.


hello-node-svc-nodeport.yaml


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 type: NodePort

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

     nodePort: 30036


아래 그림과 같은 구조가 된다.




이를 간단하게 테스트 해보자.

아래는 구글 클라우드에서 쿠버네티스 테스트 환경에서 노드로 사용되고 있는 3개의 VM 목록과 IP 주소이다.


현재 노드는 아래와 같이 3개의 노드가 배포되어 있고 IP 는 10.146.0.8~10이다.

내부 IP이기 때문에, VPC 내의 내부 IP를 가지고 있는 서버에서 테스트를 해야 한다.


같은 내부 IP를 가지고 있는 envoy-ubuntu 라는 머신 (10.146.0.18)에서 각 노드의 30036 포트로 curl을 테스트해본 결과 아래와 같이 모든 노드의 IP를 통해서 서비스 접근이 가능한것을 확인할 수 있다.



ExternalName

ExternalName은 외부 서비스를 쿠버네티스 내부에서 호출하고자할때 사용할 수 있다.

쿠버네티스 클러스터내의 Pod들은 클러스터 IP를 가지고 있기 때문에 클러스터 IP 대역 밖의 서비스를 호출하고자 하면, NAT 설정등 복잡한 설정이 필요하다.

특히 AWS 나 GCP와 같은 클라우드 환경을 사용할 경우 데이타 베이스나, 또는 클라우드에서 제공되는 매지니드 서비스 (RDS, CloudSQL)등을 사용하고자할 경우에는 쿠버네티스 클러스터 밖이기 때문에, 호출이 어려운 경우가 있는데, 이를 쉽게 해결할 수 있는 방법이 ExternalName 타입이다.

아래와 같이 서비스를 ExternalName 타입으로 설정하고, 주소를 DNS로  my.database.example.com으로 설정해주면 이 my-service는 들어오는 모든 요청을 my.database.example.com 으로 포워딩 해준다. (일종의 프록시와 같은 역할)

kind: Service
apiVersion: v1
metadata:
 name: my-service
 namespace: prod
spec:
 type: ExternalName
 externalName: my.database.example.com

다음과 같은 구조로 서비스가 배포된다.



DNS가 아닌 직접 IP를 이용하는 방식

위의 경우 DNS를 이용하였는데, DNS가 아니라 직접 IP 주소를 이용하는 방법도 있다.

서비스 ClusterIP 서비스로 생성을 한 후에, 이 때 서비스에 속해있는 Pod를 지정하지 않는다.

apiVersion: v1

kind: Service

metadata:

 name: external-svc-nginx

spec:

 ports:

 - port: 80



다음으로, 아래와 같이 서비스의 EndPoint를 별도로 지정해주면 된다.

apiVersion: v1

kind: Endpoints

metadata:

 name: external-svc-nginx

subsets:

 - addresses:

   - ip: 35.225.75.124

   ports:

   - port: 80


이 때 서비스명과 서비스 EndPoints의 이름이 동일해야 한다. 위의 경우에는 external-svc-nginx로 같은 서비스명을 사용하였고 이 서비스는 35.225.75.124:80 서비스를 가르키도록 되어 있다.

그림으로 구조를 표현해보면 다음과 같다.




35.225.75.124:80 은 nginx 웹서버가 떠 있는 외부 서비스이고, 아래와 같이 간단한 문자열을 리턴하도록 되어 있다.



이를 쿠버네티스 내부 클러스터의 Pod 에서 curl 명령을 이용해서 호출해보면 다음과 같이 외부 서비스를 호출할 수 있음을 확인할 수 있다.

Headless Service

서비스는 접근을 위해서 Cluster IP 또는 External IP 를 지정받는다.

즉 서비스를 통해서 제공되는 기능들에 대한 엔드포인트를 쿠버네티스 서비스를 통해서 통제하는 개념인데, 마이크로 서비스 아키텍쳐에서는 기능 컴포넌트에 대한 엔드포인트 (IP 주소)를 찾는 기능을 서비스 디스커버리 (Service Discovery) 라고 하고, 서비스의 위치를 등록해놓는 서비스 디스커버리 솔루션을 제공한다. Etcd 나 hashcorp의 consul (https://www.consul.io/)과 같은 솔루션이 대표적인 사례인데, 이 경우 쿠버네티스 서비스를 통해서 마이크로 서비스 컴포넌트를 관리하는 것이 아니라, 서비스 디스커버리 솔루션을 이용하기 때문에, 서비스에 대한 IP 주소가 필요없다.

이런 시나리오를 지원하기 위한 쿠버네티스의 서비스를 헤드리스 서비스 (Headless service) 라고 하는데, 이러한 헤드리스 서비스는 Cluster IP등의 주소를 가지지 않는다. 단 DNS이름을 가지게 되는데, 이 DNS 이름을 lookup 해보면, 서비스 (로드밸런서)의 IP 를 리턴하지 않고, 이 서비스에 연결된 Pod 들의 IP 주소들을 리턴하게 된다.


간단한 테스트를 해보면


와 같이 기동중인 Pod들이 있을때, Pod의 IP를 조회해보면 다음과 같다.


10.20.0.25,10.20.0.22,10.20.0.29,10.20.0.26 4개가 되는데,

다음 스크립트를 이용해서 hello-node-svc-headless 라는 헤드리스 서비스를 만들어보자


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc-headless

spec:

 clusterIP: None

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080


아래와 같이 ClusterIP가 할당되지 않음을 확인할 수 있다.



다음 쿠버네티스 클러스터내의 다른 Pod에서 nslookup으로 해당 서비스의 dns 이름을 조회해보면 다음과 같이 서비스에 의해 제공되는 pod 들의 IP 주소 목록이 나오는 것을 확인할 수 있다.




Service discovery

그러면 생성된 서비스의 IP를 어떻게 알 수 있을까? 서비스가 생성된 후 kubectl get svc를 이용하면 생성된 서비스와 IP를 받아올 수 있지만, 이는 서비스가 생성된 후이고, 계속해서 변경되는 임시 IP이다.

DNS를 이용하는 방법

가장 쉬운 방법으로는 DNS 이름을 사용하는 방법이 있다.

서비스는 생성되면 [서비스 명].[네임스페이스명].svc.cluster.local 이라는 DNS 명으로 쿠버네티스 내부 DNS에 등록이 된다. 쿠버네티스 클러스터 내부에서는 이 DNS 명으로 서비스에 접근이 가능한데, 이때 DNS에서 리턴해주는 IP는 외부 IP (External IP)가 아니라 Cluster IP (내부 IP)이다.


아래 간단한 테스트를 살펴보자. hello-node-svc 가 생성이 되었는데, 클러스터내의 pod 중 하나에서 ping으로 hello-node-svc.default.svc.cluster.local 을 테스트 하니, hello-node-svc의 클러스터 IP인 10.23.241.62가 리턴되는 것을 확인할 수 있다.



External IP (외부 IP)

다른 방식으로는 외부 IP를 명시적으로 지정하는 방식이 있다. 쿠버네티스 클러스터에서는 이 외부 IP를 별도로 관리하지 않기 때문에, 이 IP는 외부에서 명시적으로 관리되어야 한다.


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 externalIPs:

 - 80.11.12.11

 

외부 IP는 Service의 spec 부분에서 externalIPs 부분에 IP 주소를 지정해주면 된다.

구글 클라우드의 경우

퍼블릭 클라우드 (AWS, GCP 등)의 경우에는 이 방식 보다는 클라우드내의 로드밸런서를 붙이는 방법을 사용한다.


구글 클라우드의 경우를 살펴보자.서비스에 정적인 IP를 지정하기 위해서는 정적 IP를 생성해야 한다. 구글 클라우드 콘솔내의 VPC 메뉴의 External IP 메뉴에서 생성해도 되고, 아래와 같이 gcloud CLI 명령어를 이용해서 생성해도 된다.


IP를 생성하는 명령어는 gcloud compute addresses create [IP 리소스명] --region [리전]

을 사용하면 된다. 구글 클라우드의 경우에는 특정 리전만 사용할 수 있는 리저널 IP와, 글로벌에 모두 사용할 있는 IP가 있는데, 서비스에서는 리저널 IP만 사용이 가능하다. (글로벌 IP는 후에 설명하는 Ingress에서 사용이 가능하다.)

아래와 같이

%gcloud compute addresses create hello-node-ip-region  --region asia-northeast1

명령어를 이용해서 asia-northeast1 리전 (일본)에 hello-node-ip-region 이라는 이름으로 Ip를 생성하였다. 생성된 IP는 describe 명령을 이용해서 확인할 수 있으며, 아래 35.200.64.17 이 배정된것을 확인할 수 있다.



이 IP는 서비스가 삭제되더라도 계속 유지되고, 다시 재 사용이 가능하다.

그러면 생성된 IP를 service에 적용해보자

다음과 같이 hello-node-svc-lb-externalip.yaml  파일을 생성하자


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer

 loadBalancerIP: 35.200.64.17


타입을 LoadBalancer로 하고, loadBalancerIP 부분에 앞에서 생성한 35.200.64.17 IP를 할당한다.

다음 이 파일을 kubectl create -f hello-node-svc-lb-externalip.yaml 명령을 이용해서 생성하면, hello-node-svc 가 생성이 되고, 아래와 같이 External IP가 우리가 앞에서 지정한 35.200.64.17 이 지정된것을 확인할 수 있다.




본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. js jang 2018.06.19 11:57  댓글주소  수정/삭제  댓글쓰기

    안그래도 service 개념 정리중이었는데 좋은 정리 공유 감사합니다! 한가지 궁금한점이 있는데요 NodePort 설명에 포함된 다이어그림을 보면 hello-node 가 위치한 pod 의 port 가 service.yaml 에서 정의한 targetport 8080 이 되는게 맞는건가요?

  2. js jang 2018.06.19 12:18  댓글주소  수정/삭제  댓글쓰기

    다시 한번 좋은 공유 감사합니다:)

  3. jtkim 2019.05.10 09:52  댓글주소  수정/삭제  댓글쓰기

    쿠버네티스 공부에 많은 도움을 받고 있어 감사드려요.
    내용 중에 node port 설명 부분에서 nodeport 지정하면 targetPort 는 없는게 맞는지요?

  4. pco 2019.06.06 19:16  댓글주소  수정/삭제  댓글쓰기

    정말 쿠버네티스 테스트해보고싶어서 해봤는데 너무 감사합니다.....

쿠버네티스 #6

Replication Controller를 이용하여 서비스 배포하기

조대협 (http://bcho.tistory.com)


1. 도커 파일 만들기

node.js로 간단한 웹서버를 만들어서 도커로 패키징 해보자.

실습을 진행하기 위해서 로컬 환경에 도커와, node.js 가 설치되어 있어야 한다. 이 두 부분은 생략하도록 한다.

여기서 사용한 실습 환경은 node.js carbon 버전 (8.11.3), 도커 맥용 18.05.0-ce, build f150324 을 사용하였다.

node.js 애플리케이션 준비하기

node.js로 간단한 웹 애플리케이션을 제작해보자 server.js라는 이름으로 아래 코드를 작성한다.

var os = require('os');

 

var http = require('http');

var handleRequest = function(request, response) {

 response.writeHead(200);

 response.end("Hello World! I'm "+os.hostname());

 

 //log

 console.log("["+

               Date(Date.now()).toLocaleString()+

               "] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


이 코드는 8080 포트로 웹서버를 띄워서 접속하면 “Hello World!” 문자열과 함께, 서버의 호스트명을 출력해준다. 그리고 stdout에 로그로, 시간과 서버의 호스트명을 출력해준다.

코드 작성이 끝났으면, 서버를 실행해보자

%node server.js


다음 브라우저로 접속하면 다음과 같은 결과를 얻을 수 있다.


그리고 콘솔화면에는 아래와 같이 시간과 호스트명이 로그로 함께 출력된다.

도커로 패키징하기

그러면 이 node.js 애플리케이션을 도커 컨테이너로 패키징 해보자

Dockerfile 이라는 파일을 만들고 아래 코드를 작성한다.

FROM node:carbon

EXPOSE 8080

COPY server.js .

CMD node server.js > log.out


이 코드는 node.js carborn (8.11.3) 컨테이너 이미지를 베이스로 한후에,  앞서 작성한 server.js 코드를 복사한후에, node server.js > log.out 명령어를 실행하도록 하는 컨테이너를 만드는 설정파일이다.

설정 파일이 준비되었으면,  도커 컨테이너 파일을 만들어보자


% docker build -t gcr.io/terrycho-sandbox/hello-node:v1 .


docker build  명령은 컨테이너를 만드는 명령이고, -t는 빌드될 이미지에 대한 태그를 정하는 명령이다.

빌드된 컨테이너 이미지는 gcr.io/terrycho-sandbox/hello-node로  태깅되는데, 이는 향후에 구글 클라우드 컨테이너 레지스트리에 올리기 위해서 태그 명을 구글 클라우드 컨테이너 레지스트리의 포맷을 따른 것이다. (참고 https://cloud.google.com/container-registry/docs/pushing-and-pulling)

포맷은 [HOST_NAME]/[GOOGLE PROJECT-ID]/[IMAGE NAME]


gcr.io/terrycho-sandbox는 도커 이미지가 저장될 리파지토리의 경로를 위의 규칙에 따라 정의한 것인데,

  • gcr.io는 구글 클라우드 컨테이너 리파지토리 US 리전을 지칭하며,

  • terrycho-sandbox는 본인의 구글 프로젝트 ID를 나타낸다.

  • 이미지명을 hello-node 로 지정하였다.

  • 마지막으로 콜론(:) 으로 구별되어 정의한 부분은 태그 부분으로, 여기서는 “v1”으로 태깅을 하였다.


이미지는 위의 이름으로 지정하여 생성되어 로컬에 저장된다.




빌드를 실행하면 위와 같이 node:carbon 이미지를 읽어와서 필요한 server.js 파일을 복사하고 컨테이너 이미지를 생성한다.

컨테이너 이미지가 생성되었으면 로컬 환경에서 이미지를 기동 시켜보자


%docker run -d -p 8080:8080 gcr.io/terrycho-sandbox/hello-node:v1


명령어로 컨테이너를 실행할 수 있다.

  • -d 옵션은 컨테이너를 실행하되, 백그라운드 모드로 실행하도록 하였다.

  • -p는 포트 맵핑으로 뒤의 포트가 도커 컨테이너에서 돌고 있는 포트이고, 앞의 포트가 이를 밖으로 노출 시키는 포트이다 예를 들어 -p 9090:8080 이면 컨테이너의 8080포트를 9090으로 노출 시켜서 서비스 한다는 뜻이다. 여기서는 컨테이너 포트와 서비스로 노출 되는 포트를 동일하게 8080으로 사용하였다.


컨테이너를 실행한 후에, docker ps 명령어를 이용하여 확인해보면 아래와 같이 hello-node:v1 이미지로 컨테이너가 기동중인것을 확인할 수 있다.



다음 브라우져를 통해서 접속을 확인하기 위해서 localhost:8080으로 접속해보면 아래와 같이 Hello World 와 호스트명이 출력되는 것을 확인할 수 있다.


로그가 제대로 출력되는지 확인하기 위해서 컨테이너 이미지에 쉘로 접속해보자

접속하는 방법은


% docker exec -i -t [컨테이너 ID] /bin/bash

를 실행하면 된다. 컨테이너 ID 는 앞의 docker ps 명령을 이용하여 기동중인 컨테이너 명을 보면 처음 부분이 컨테이너 ID이다.

hostname 명령을 실행하여 호스트명을 확인해보면 위에 웹 브라우져에서 출력된 41a293ba79a7과 동일한것을 확인할 수 있다. 디렉토리에는 server.js 파일이 복사되어 있고, log.out 파일이 생성된것을 볼 수 있다.  

cat log.out을 이용해서 보면, 시간과 호스트명이 로그로 출력된것을 확인할 수 있다.



2. 쿠버네티스 클러스터 준비

구글 클라우드 계정 준비하기

구글 클라우드 계정 생성은 http://bcho.tistory.com/1107 문서를 참고하기 바란다.

쿠버네티스 클러스터 생성하기

쿠버네티스 클러스터를 생성해보자, 클러스터 생성은 구글 클라우드 콘솔의 Kubernetes Engine > Clusters 메뉴에서 Create 를 선택하면 클러스터 생성이 가능하다.



클러스터 이름을 넣어야 하는데, 여기서는 terry-gke-10 을 선택하였다. 구글 클라우드에서 쿠버네티스 클러스터는 싱글 존에만 사용가능한 Zonal 클러스터와 여러존에 노드를 분산 배포하는 Regional 클러스터 두 가지가 있는데, 여기서는 하나의 존만 사용하는 Zonal 클러스터를 설정한다. (Regional은 차후에 다루도록 하겠다.)

다음 클러스터를 배포한 존을 선택하는데, asia-northeast1-c (일본)을 선택하였다.

Cluster Version은 쿠버네티스 버전인데, 1.10.2 버전을 선택한다.

그리고 Machine type은 쿠버네티스 클러스터의 노드 머신 타입인데, 간단한 테스트 환경이기 때문에,  2 CPU에 7.5 메모리를 지정하였다.

다음으로 Node Image는 노드에 사용할 OS 이미지를 선택하는데, Container Optimized OS를 선택한다. 이 이미지는 컨테이너(도커)를 운영하기 위해 최적화된 이미지이다.

다음으로는 노드의 수를 Size에서 선택한다. 여기서는 3개의 노드를 운용하도록 설정하였다.


아래 부분에 보면  Automatic node upgrades 라는 기능이 있다.


구글 클라우드의 재미있는 기능중 하나인데, 쿠버네티스 버전이 올라가면 자동으로 버전을 업그레이드 해주는 기능으로, 이 업그레이드는 무정지로 진행 된다.


gcloud 와 kubectl 설치하기

클러스터 설정이 끝났으면 gloud (Google Cloud SDK 이하 gcloud)를 인스톨한다.

gcloud 명령어의 인스톨 방법은 OS마다 다른데, https://cloud.google.com/sdk/docs/quickstarts 문서를 참고하면 된다.

별다른 어려운 작업은 없고, 설치 파일을 다운 받아서 압축을 푼후에, 인스톨 스크립트를 실행하면 된다.


kubectl은 쿠버네티스의 CLI (Command Line Interface)로, gcloud를 인스톨한후에,

%gcloud components install kubectl

명령을 이용하면 인스톨할 수 있다.

쿠버네티스 클러스터 인증 정보 얻기

gcloud와 kubectl 명령을 설치하였으면, 이 명령어들을 사용할때 마다 쿠버네티스에 대한 인증이 필요한데, 인증에 필요한 인증 정보는 아래 명령어를 이용하면, 자동으로 사용이 된다.

gcloud container clusters get-credentials CLUSTER_NAME

여기서는 클러스터명이 terry-gke10이기 때문에,

%gcloud container clusters get-credentials terry-gke-10

을 실행한다.


명령어 설정이 끝났으면, gcloud 명령이 제대로 작동하는지를 확인하기 위해서, 현재 구글 클라우드내에 생성된 클러스터 목록을 읽어오는 gcloud container clusters list 명령어를 실행해보자



위와 같이 terry-gke-10 이름으로 asia-northeast1-c 존에 쿠버네티스 1.10.2-gke.3 버전으로 클러스터가 생성이 된것을 볼 수 있고, 노드는 총 3개의 실행중인것을 확인할 수 있다.

3. 쿠버네티스에 배포하기

이제 구글 클라우드에 쿠버네티스 클러스터를 생성하였고, 사용을 하기 위한 준비가 되었다.

앞에서 만든 도커 이미지를 패키징 하여, 이 쿠버네티스 클러스터에 배포해보도록 하자.

여기서는 도커 이미지를 구글 클라우드내의 도커 컨테이너 레지스트리에 등록한 후, 이 이미지를 이용하여 ReplicationController를 통해 총 3개의 Pod를 구성하고 서비스를 만들어서 이 Pod들을 외부 IP를 이용하여 서비스를 제공할 것이다.

도커 컨테이너 이미지 등록하기

먼저 앞에서 만든 도커 이미지를 구글 클라우드 컨테이너 레지스트리(Google Container Registry 이하 GCR) 에 등록해보자.

GCR은 구글 클라우드에서 제공하는 컨테이너 이미지 저장 서비스로, 저장 뿐만 아니라, CI/CD 도구와 연동하여, 자동으로 컨테이너 이미지를 빌드하는 기능, 그리고 등록되는 컨테이너 이미지에 대해서 보안적인 문제가 있는지 보안 결함을 스캔해주는 기능과 같은 다양한 기능을 제공한다.


컨테이너 이미지를 로컬환경에서 도커 컨테이너 저장소에 저장하려면 docker push라는 명령을 사용하는데, 여기서는 GCR을 컨테이너 이미지 저장소로 사용할 것이기 때문에, GCR에 대한 인증이 필요하다.

인증은 한번만 해놓으면 되는데

%gcloud auth configure-docker

명령을 이용하면, 인증 정보가 로컬 환경에 자동으로 저장된다.



인증이 완료되었으면, docker push 명령을 이용하여 이미지를 GCR에 저장한다.

%docker push gcr.io/terrycho-sandbox/hello-node:v1


명령어를 실행하면, GCR에 hello-node 이미지가 v1 태그로 저장된다.


이미지가 GCR에 잘 저장되었는지를 확인하기 위해서 구글 클라우드 콘솔에 Container Registry (GCR)메뉴에서 Images라는 메뉴를 들어가보자




아래와 같이 hello-node 폴더에 v1이라는 태그로 이미지가 등록된것을 확인할 수 있다.

ReplicationController 등록

컨테이너 이미지가 등록되었으면 이 이미지를 이용해서 Pod를 생성해보자,  Pod 생성은 Replication Controller (이하 rc)를 생성하여, rc가 Pod 생성 및 컨트롤을 하도록 한다.


다음은 rc 생성을 위한 hello-node-rc.yaml 파일이다.


apiVersion: v1

kind: ReplicationController

metadata:

 name: hello-node-rc

spec:

 replicas: 3

 selector:

   app: hello-node

 template:

   metadata:

     name: hello-node-pod

     labels:

       app: hello-node

   spec:

     containers:

     - name: hello-node

       image: gcr.io/terrycho-sandbox/hello-node:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080


hello-node-rc 라는 이름으로 rc를 생성하는데, replica 를 3으로 하여, 총 3개의 pod를 생성하도록 한다.

템플릿 부분에 컨테이너 스팩에 컨테이너 이름은 hello-node로 하고 이미지는 앞서 업로드한 gcr.io/terrycho-sandbox/hello-node:v1 를 이용해서 컨테이너를 만들도록 한다. 컨테이너의 포트는 8080을 오픈한다. 템플릿 부분에서 app 이라는 이름의 라벨을 생성하고 그 값을 hello-node로 지정하였다. 이 라벨은 나중에 서비스 (service)에 의해 외부로 서비스될 pod들을 선택하는데 사용 된다.


여기서 imagePullPolicy:Always  라고 설정한 부분이 있는데, 이는 Pod를 만들때 마다 매번 컨테이너 이미지를 확인해서 새 이미지를 사용하도록 하는 설정이다.  컨테이너 이미지는 한번 다운로드가 되면 노드(Node) 에 저장이 되어 있게 되고, 사용이 되지 않는 이미지 중에 오래된 이미지는 Kublet이 가비지 컬렉션 (Garbage collection) 정책에 따라 이미지를 삭제하게 되는데, 문제는 노드에 이미 다운되어 있는 이미지가 있을 경우 컨테이너 생성시 노드에 이미 다운로드 되어 있는 이미지를 사용한다. 컨테이너 리파지토리에 같은 이름으로 이미지를 업데이트 하거나 심지어 그 이미지를 삭제하더라도 노드에 이미지가 이미 다운로드 되어 있으면 다운로드된 이미지를 사용하기 때문에, 업데이트 부분이 반영이 안된다.

이를 방지하기 위해서 imagePullPolicy:Always로 해주면 컨테이너 생성시마다 이미지 리파지토리를 검사해서 새 이미지를 가지고 오기 때문에, 업데이트된 내용을 제대로 반영할 수 있다.


%kubectl create -f hello-node-rc.yaml


명령어를 실행해서 rc와 pod를 생성한다.




위의 그림과 같이 3개의 Pod가 생성된것을 확인할 수 있는데, Pod가 제대로 생성되었는지 확인하기 위해서 hello-node-rc-rsdzl pod에서 hello-node-rc-2phgg pod의 node.js 웹서버에 접속을 해볼 것이다.

아직 서비스를 붙이지 않았기 때문에, 이 pod들은 외부 ip를 이용해서 서비스가 불가능하기 때문에, 쿠버네티스 클러스터 내부의 pod를 이용하여 내부 ip (private ip)간에 통신을 해보기 위해서 pod에서 pod를 호출 하는 것이다. kubectl describe pod  [pod 명] 명령을 이용하면, 해당 pod의 정보를 볼 수 있다. hello-node-rc-2hpgg pod의 cluster ip (내부 ip)를 확인해보면 10.20.1.27 인것을 확인할 수 있다.


kubectl exec 명령을 이용하면 쉘 명령어를 실행할 수 있는데, 다음과 같이 hello-node-rc-rsdzl pod에서 첫번째 pod인 hello-node-rc-2phgg의 ip인 10.20.1.27의 8080 포트로 curl 을 이용해 HTTP 요청을 보내보면 다음과 같이 정상적으로 응답이 오는 것을 볼 수 있다.


Service 등록

rc와 pod 생성이 끝났으면 이제 서비스를 생성해서 pod들을 외부 ip로 서비스 해보자

다음은 서비스를 정의한 hello-node-svc.yaml 파일이다.


hello-node-svc.yaml

apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer


Selector 부분에 app:hello-node 로 지정하여, pod들 중에 라벨의 키가 app이고 값이 hello-node인 pod 들만 서비스에 연결하도록 지정하였다. 다음 서비스의 포트는 80으로 지정하였고, pod의 port는 8080으로 지정하였다.


서비스가 배포되면 위와 같은 구조가 된다.

%kubectl create -f hello-node-svc.yaml

명령을 이용하면 서비스가 생성이 된다.


다음 생성된 서비스의 외부 ip를 얻기 위해서 kubectl get svc 명령을 실행해보자

아래 그림과 같이 35.200.40.161 IP가 할당된것을 확인할 수 있다.


이 IP로 접속을 해보면 아래와 같이 정상적으로 응답이 오는 것을 확인할 수 있다.


RC 테스트

rc는 pod의 상태를 체크하다가 문제가 있으면 다시, pod를 기동해주는 기능을 한다.

이를 테스트하기 위해서 강제적으로 모든 pod를 제거해보자. kubectl delete pod --all을 이용하면 모든 pod를 제거할 수 있는데, 아래 그림을 보면, 모든 pod를 제거했더니 3개의 pod가 제거되고 새롭게 3개의 pod가 기동되는 것을 확인할 수 있다.



운영중에 탄력적으로 pod의 개수를 조정할 수 있는데, kubectl scale 명령을 이용하면 된다.

kubectl scale --replicas=[pod의 수] rc/[rc 명] 식으로 사용하면 된다. 아래는 pod의 수를 4개로 재 조정한 내용이다.



자원 정리

테스트가 끝났으면 서비스, rc,pod를 삭제해보자.

  • 서비스 삭제는 kubectl delete svc --all 명령어를 이용한다.

  • rc 삭제는 kubectl delete rc --all

  • pod 삭제는 kubectl delete pod --all

을 사용한다.

삭제시 주의할점은 pod를 삭제하기 전에 먼저 rc를 삭제해야 한다. 아니면, pod가 삭제된 후 rc에 의해서 다시 새로운 pod가 생성될 수 있다.


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. Racoon 2018.07.04 17:22  댓글주소  수정/삭제  댓글쓰기

    쿠버네티스 참 막막했는데, 좋은글 덕에 정말 많이 이해가 됐습니다! 감사합니다ㅠㅠ 근데.. 한가지 궁금한 게 있는데요.. 현재 Minikube으로 테스트를 하고 있는데, Minikube의 경우에는 LoadBalancer를 적용할 수 없는건가요?

  2. jkh 2019.06.12 15:49  댓글주소  수정/삭제  댓글쓰기

    안녕하세요!! 올려주신 자료 덕분에 블로그를 통해 쿠버네티스를 공부하고 있는 초보개발자입니다. 여기서 컨테이너포트를 8080으로 설정한 것은 server.js에서 8080으로 설정해서 그런것인가요? 감사합니다