Label, Deployment, Service

|

Label

Kubenetes를 이용해 큰 규모의 서비스를 구동하게 되면 다수의 Pod들을 운영하게 될 수 있습니다. 이 경우 다수의 Pod들을 좀 더 편리하게 관리하기 위해서 Kubenetes에서는 Label이라는 기능을 제공하고 있습니다. Label은 key-value 데이터 쌍 형태로 되어 있습니다.

Pod definition에 Label을 추가하는 방법은 다음과 같습니다.

  labels:
    app: nginx


pod-nginx-with-label.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80


다음 명령어를 이용해 Pod를 생성하고 nginx라는 Label을 가진 Pod 리스트를 조회할 수 있습니다.

$ kubectl create -f ./pod-nginx-with-label.yaml

$ kubectl get pods -l app=nginx


Deployment

이제 다수의 컨테이너, Label이 붙은 Pod 등을 이용해서 App을 만들 수 있게 되었지만, 여전히 많은 의문점이 생길 수 있습니다. 예를 들어 Pod를 어떻게 Scaling 할 것인지, 새로운 릴리즈를 어떻게 Roll Out 할 것인지 등의 이슈가 발생할 수 있습니다.

이 경우 Deployment를 이용해서 Pod 들을 관리하거나 업데이트할 수 있습니다.

Deployment Object에는 Pod를 생성하는 템플릿이나 희망 Replica 개수를 정의할 수 있습니다. Label Selector를 이용해서 Pods를 생성하거나 제거하기도 하며, 새로운 업데이트나 릴리즈를 현재 구동중인 Pod들에 안전하게 반영할 수 있게 도와줍니다.


deployment.yaml

다음은 2개의 nginx Pod가 정의된 deployment.yaml 파일입니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # tells deployment to run 2 pods matching the template
  template: # create pods using pod definition in this template
    metadata:
      # unlike pod-nginx.yaml, the name is not included in the meta data as a unique name is
      # generated from the deployment name
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80


Deployment 생성

다음 명령어를 이용해서 Deployment를 생성할 수 있습니다.

$ kubectl create -f ./deployment.yaml

$ kubectl get deployment

$ kubectl get pods -l app=nginx


Deployment 업그레이드하기

만약 위의 Deployment에서 nginx 컨테이너의 버전을 1.7.9에서 1.8로 업그레이드하는 경우 다음과 같은 작업을 수행하면 됩니다.

deployment-update.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.8 # Update the version of nginx from 1.7.9 to 1.8
        ports:
        - containerPort: 80


$ kubectl apply -f ./deployment-update.yaml

$ kubectl get pods -l app=nginx


Deployment 삭제

Deployment 삭제는 다음 명령어로 수행합니다.

$ kubectl delete deployment nginx-deployment


Services

service.yaml

Once you have a replicated set of Pods, you need an abstraction that enables connectivity between the layers of your application. For example, if you have a Deployment managing your backend jobs, you don’t want to have to reconfigure your front-ends whenever you re-scale your backends. Likewise, if the Pods in your backends are scheduled (or rescheduled) onto different machines, you can’t be required to re-configure your front-ends. In Kubernetes, the service abstraction achieves these goals. A service provides a way to refer to a set of Pods (selected by labels) with a single static IP address. It may also provide load balancing, if supported by the provider.

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  ports:
  - port: 8000 # the port that this service should serve on
    # the container on each pod to connect to, can be a name
    # (e.g. 'www') or a number (e.g. 80)
    targetPort: 80
    protocol: TCP
  # just like the selector in the deployment,
  # but this time it identifies the set of pods to load balance
  # traffic to.
  selector:
    app: nginx


Service 관리

$ kubectl create -f ./service.yaml

$ kubectl get services

$ kubectl delete service nginx-service

When created, each service is assigned a unique IP address. This address is tied to the lifespan of the Service, and will not change while the Service is alive. Pods can be configured to talk to the service, and know that communication to the service will be automatically load-balanced out to some Pod that is a member of the set identified by the label selector in the Service.

Pod, Volumes, Multiple Containers

|

Pod

Kubenetes에서 Pod는 하나 이상의 컨테이너 그룹을 의미합니다. 하나의 Pod 안에 있는 컨테이너들은 동시에 배포되며 시작, 정지, 교체(Replicated) 등의 작업도 동시에 이루어집니다.


Pod Definition

간단한 Pod 예제는 다음과 같습니다. yaml 형태의 파일로 표현합니다.

pod-nginx.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80


Pod Management

아래의 명령어로 Pod를 생성할 수 있습니다.

$ kubectl create -f ./pod-nginx.yaml

$ kubectl get pods


사용이 끝난 pod는 삭제를 해줍니다.

$ kubectl delete pod nginx


Volumes

일반적으로 컨테이너 안의 데이터는 컨테이너의 생명 주기가 끝나면 소멸됩니다. 영구적인 데이터 저장을 위해서 Pod에 호스팅 컴퓨터의 저장소를 Volume이라는 이름으로 마운트해서 사용할 수 있습니다.

Volume의 정의는 다음과 같이 할 수 있습니다.

volumes:
    - name: redis-persistent-storage
        emptyDir: {}

그리고 컨테이너 안에는 위에서 정의한 Volume과 동일한 이름으로 마운트할 수 있습니다.

volumeMounts:
    # name must match the volume name defined in volumes
    - name: redis-persistent-storage
      # mount path within the container
      mountPath: /data/redis


pod-redis.yaml

Volume 및 마운트를 정의한 Pod definition은 다음과 같습니다.

apiVersion: v1
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: redis
    volumeMounts:
    - name: redis-persistent-storage
      mountPath: /data/redis
  volumes:
  - name: redis-persistent-storage
    emptyDir: {}


Volume 타입으로는 다음과 같은 종류가 있습니다.

  • EmptyDir : Pod가 동작하는 동안 존재하는 새로운 디렉토리를 생성함. Pod가 죽거나 재시작되어도 데이터는 유지됨
  • HostPath : 호스트에 이미 존재하는 파일 시스템 (ex. /var/logs)


Multiple Containers

다음과 같이 Pod definition 안에는 복수의 컨테이너를 정의를 할 수 있습니다.

apiVersion: v1
kind: Pod
metadata:
  name: www
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - mountPath: /srv/www
      name: www-data
      readOnly: true
  - name: git-monitor
    image: kubernetes/git-monitor
    env:
    - name: GIT_REPO
      value: http://github.com/some/repo.git
    volumeMounts:
    - mountPath: /data
      name: www-data
  volumes:
  - name: www-data
    emptyDir: {}

Volume은 한 군데서 정의를 했지만 nginxgit-monitor라는 이름의 컨테이너에서 해당 Volume을 동시에 참고하고 있는 것을 확인할 수 있습니다.

Custom Node.js App 배포해보기

|

Custom Node.js App 배포

샘플 node.js 파일 생성

먼저 다음과 같은 server.js 파일을 생성합니다.

var http = require('http');

var handleRequest = function(request, response) {
  console.log('Received request for URL: ' + request.url);
  response.writeHead(200);
  response.end('Hello SnowDeer!');
};
var www = http.createServer(handleRequest);
www.listen(8080);


정상적으로 동작하는지 확인은

node server.js

명령 수행 후 웹브라우저에 접속해서 확인할 수 있습니다.


Dockerfile 생성

그리고 다음과 같은 Dockerfile 파일을 생성합니다.

FROM node:6.9.2
EXPOSE 8080
COPY server.js .
CMD node server.js

다음 명령어를 이용해서 Docker 이미지를 빌드합니다.

docker build -t snowdeer/hello-nodejs:v1 .

그리고

docker run --rm --name snowdeer -d -p 8080:8080 snowdeer/hello-nodejs:v1

와 같이 Docker 컨테이너를 생성한다음 웹브라우저에서 접속해서 해당 컨테이너가 잘 생성되는지 확인합니다.


Docker Hub에 이미지 업로드

Kubenetes에서 Docker 이미지를 이용해서 Deployment를 생성하기 위해서는 해당 이미지가 Docker Hub에 등록이 되어 있어야 합니다. 다음 명령어를 이용해서 위에서 생성한 이미지를 Docker Hub에 push 합니다.

docker push snowdeer/hello-nodejs:v1


Deployment 생성

Kubenetes에서 Pod는 관리 또는 네트워크 목적으로 묶여있는 하나 이상의 컨테이너들의 그룹입니다. kubectl run 명령어를 이용해서 Deployment를 생성할 수 있습니다. Deployment는 Kubenetes에서 Pod들의 생성과 스케일링(Scaling)을 위해 추천하는 방식입니다.

다음 명령어를 이용하여 Docker Hub에 등록되어 있는 hello-nodejs:v1 이미지로부터 Deployment를 생성합니다.

kubectl run hello-nodejs --image=snowdeer/hello-nodejs:v1 --port=8080

다음 명령어로 Deployment 정보를 확인할 수 있습니다.

$ kubectl get deployment

NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-nodejs   1         1         1            1           1m

다음 명령어로 Pods 정보를 확인할 수 있습니다.

$ kubectl get pods

NAME                            READY     STATUS    RESTARTS   AGE
hello-nodejs-868459c9d5-hghv8   1/1       Running   0          3m

만약 이 과정에서 AVAILABLE 항목이나 READY 값이 1이 되지 않는다면, kubectl describe pod [pod name] 명령어를 이용해서 에러 원인을 찾아야 합니다. 보통 위에서 생성한 이미지가 Docker Hub에 등록되지 않아서 이미지를 pull 하는 과정에서 오류가 발생하는 경우가 많이 있습니다.


다음 명령어를 이용해서 클러스터의 이벤트 로그를 조회할 수 있습니다.

kubectl get events

다음 명령어로 kubectl 설정 정보를 조회할 수 있습니다.

$ kubectl config view

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://172.31.5.20:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED


Service 생성

기본적으로 Pod를 생성할 경우 클러스터 내부에서만 접근가능한 내부 IP Address로 설정이 됩니다. 그래서 위에서 생성한 hello-nodejs 컨테이너를 외부 네트워크에서도 접속할 수 있게 하려면 해당 Pod를 Kubenetes Service로 노출(Expose)시켜줘야 합니다.

$ kubectl expose deployment hello-nodejs --type=LoadBalancer

service "hello-nodejs" exposed

위 명령어를 수행한 다음 kubectl get services 명령어를 실행하면 다음과 같이 출력됩니다.

$ kubectl get services

NAME           TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
hello-nodejs   LoadBalancer   10.103.63.176   <pending>     8080:31853/TCP   19s
kubernetes     ClusterIP      10.96.0.1       <none>        443/TCP          1h

--type=LoadBalancer 옵션을 이용해서 해당 Service를 외부 네트워크에 노출할 수 있습니다. (Load Balance 기능을 제공하는 클라우드 서비스 제공자들이 해당 서비스에 외부 IP Address를 부여할 것입니다.)

이제 웹브라우저를 통해 접속을 해봅니다. 위의 kubectl get services 명령어 출력 결과를 보면 8088 포트가 31583 포트로 포워딩된 것을 알 수 있습니다. 접속 주소를 [IP Address]:31583으로 시도해봅니다.



소스 업데이트

이제 위에서 작성한 server.js 파일의 내용을 수정하고 그 내용을 컨테이너에 반영해보도록 합시다.

var http = require('http');

var handleRequest = function(request, response) {
  console.log('Received request for URL: ' + request.url);
  response.writeHead(200);
  response.end('Hello SnowDeer! This is the send version !!!');
};
var www = http.createServer(handleRequest);
www.listen(8080);


Docker 이미지를 새로 빌드하고 Docker Hub에 push 합니다.

docker build -t snowdeer/hello-nodejs:v2 .

docker push snowdeer/hello-nodejs:v2


Deployment의 이미지 업데이트를 해줍니다.

$ kubectl set image deployment/hello-nodejs hello-nodejs=snowdeer/hello-nodejs:v2

deployment "hello-nodejs" image updated

잠시 후 웹브라우저로 기존 주소에 접속해보면 변경된 내용을 확인할 수 있습니다.

Docker Hub에 이미지 업로드

|

Docker Hub에 이미지 업로드하는 방법

먼저 Docker Hub에 회원가입이 되어 있어야 합니다.


Docker 로그인

로그인은 docker login 명령어를 이용해서 할 수 있습니다.

$ docker login

Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: snowdeer
Password:
Login Succeeded


샘플 이미지 생성

Docker Hub에 이미지를 올릴 때는 이미지 이름을 [Docker Hub 사용자 계정]/[이미지 이름]:[태그] 형태로 생성해야 합니다.

Docker 이미지를 하나 생성해봅니다.


server.js

var http = require('http');

var handleRequest = function(request, response) {
  console.log('Received request for URL: ' + request.url);
  response.writeHead(200);
  response.end('Hello SnowDeer!');
};
var www = http.createServer(handleRequest);
www.listen(8080);


Dockerfile 생성

그리고 다음과 같은 Dockerfile 파일을 생성합니다.

FROM node:6.9.2
EXPOSE 8080
COPY server.js .
CMD node server.js


Docker Image 빌드

다음 명령어를 이용해서 Docker 이미지를 빌드합니다.

docker build -t snowdeer/hello-nodejs:v1 .


Docker Hub에 push

$ docker push snowdeer/hello-nodejs:v1

The push refers to a repository [docker.io/snowdeer/hello-nodejs]
9b0e32122635: Pushed
381c97ba7dc3: Mounted from library/node
604c78617f34: Mounted from library/node
fa18e5ffd316: Mounted from library/node
0a5e2b2ddeaa: Mounted from library/node
53c779688d06: Mounted from library/node
60a0858edcd5: Mounted from library/node
b6ca02dfe5e6: Mounted from library/node
v1: digest: sha256:64a2d069b8a1d5173ce7b6e6eadfec183ab63ae7857ce7ec73bfd9758fb812c0 size: 2002

The connection to the server localhost:8080 was refused 오류 발생시

|

The connection to the server localhost:8080 was refused 오류 해결법

kubectl 명령어를 입력하면 다음 오류 메시지가 발생하는 경우가 있습니다.

The connection to the server localhost:8080 was refused - did you specify the right host or port?

이 문제는 다음 명령어를 입력하면 해결이 됩니다.

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

위에서 admin.conf 파일은 kubeadm init 명령어를 수행했을 때 생성됩니다. 즉, master 노드에서만 kubectl 명령어를 사용할 수 있으며, 다른 노드에서 kubectl 명령어를 사용하고 싶을 때는 master 노드에서 생성한 admin.conf 파일을 복사해오면 일반 노드에서도 kubectl 명령어를 사용할 수 있습니다.