Docker 실습 예제 (Redis)

|

Redis 공식 이미지 사용

Redis 이미지 다운로드(pull)

$ docker pull redis

Using default tag: latest
latest: Pulling from library/redis
d2ca7eff5948: Pull complete
1d1a2245aaa6: Pull complete
9a483dd2a28b: Pull complete
7b78ebdc44f0: Pull complete
79b76500ef26: Pull complete
b2991d9a5624: Pull complete
Digest: sha256:e55dff3a21a0e7ba25e91925ed0d926d959dac09f9099fd1bcc919263305f1e4
Status: Downloaded newer image for redis:latest


Redis 컨테이너 시작

앞에서 가져온 redis 임지를 이용해서 컨테이너를 시작합니다.

$ docker run --name myredis -d redis

a16226308354840de9dadda78bb83f2378dc5cdb0270c097942357b466bf023f

-d 옵션을 주면 컨테이너를 백그라운드에서 실행시킬 수 있습니다.


컨테이너간 연결

redis 컨테이너를 시작하긴 헀지만, 그 이후에 해야 할 일들이 있습니다. 어떻게든 데이터베이스로 연결을 해야 합니다.

redis-cli 도구를 설치하기 위해서 새로운 컨테이너를 생성하고 앞서 생성한 컨테이너와 연결을 해줍니다.

$ docker run --rm -it --link myredis:redis redis /bin/bash

root@c5706ca49d45:/data# redis-cli -h redis -p 6379
redis:6379> ping
PONG

redis:6379> set "abc" 123
OK

redis:6379> get "abc"
"123"

redis:6379> exit

root@c5706ca49d45:/data# exit

exit

--link 옵션을 이용하게 되면 새로운 도커 컨테이너와 기존의 myredis 이름의 컨테이너가 연결이 됩니다. 새로운 컨테이너 안에서 myredis 컨테이너를 redis라는 이름으로 참조합니다. 이러한 작업을 수행하려면 도커는 컨테이너 /etc/hosts에서 redis를 위한 진입점을 생성하고, myredis 컨테이너의 IP 주소를 가리키도록 합니다.

이렇게 하면 Redis 컨테이너의 IP 주소를 전달하거나 찾을 필요없이 redis-cli에서 redis라는 호스트 이름을 사용할 수 있게 됩니다.


볼륨 설정

컨테이너가 종료되면 컨테이너 내부의 데이터는 사라집니다. 따라서 컨테이너안의 데이터를 유지하고 백업하기 위해서는 호스트 또는 다른 컨테이너 간에 데이터를 공유할 수 있는 설정이 필요합니다. 도커에서는 볼륨(Volume)이라는 개념을 통해 컨테이너에 파일 또는 디렉토리를 마운트(Mount)할 수 있습니다.

도커에서 볼륨을 사용하는 방법은 다음과 같이 두 가지 방법이 있습니다.

  • Dockerfile 안에 VOLUME 설정을 사용
  • docker run 명령어를 사용할 때 -v 옵션을 활용

예를 들어 컨테이너 안에 /data라는 볼륨을 생성하고 싶으면 다음과 같이 할 수 있습니다.

VOLUME /data

또는

docker run -v /data redis

기본적으로 디렉토리나 파일은 호스트의 도커 설치 디렉토리(보통은 /var/lib/docker/)안에 마운트됩니다. -v 옵션을 이용해서 호스트의 특정 디렉토리를 컨테이너 안의 디렉토리로 마운트 할 수도 있습니다.

docker run -v [호스트 디렉토리]:[컨테이너 디렉토리] reids

ex) docker run -v /home/snowdeer/docker/data:/data redis


데이터 백업

앞서 만들었던 myredis 컨테이너가 실행 중일 때 백업을 하고 싶으면 다음 명령을 이용해서 백업을 할 수 있습니다.

$ docker run --rm --volumes-from myredis -v $(pwd)/redis-backup:/backup debian cp /data/dump.rdb /backup/

위 명령을 수행하면 현재 호스트 디렉토리에 redis-backup이라는 디렉토리를 만들고 컨테이너 안의 dump.rdb 파일을 호스트로 백업하게 됩니다.


컨테이너 종료와 삭제

컨테이너 종료와 삭제 방법은 다음과 같습니다.

$ docker stop myredis

$ docker rm myredis

만약 모든 컨테이너들을 삭제하고 싶으면 다음과 같이 명령어를 내리면 됩니다.

$ docker stop $(docker ps -aq)

$ docker rm $(docker ps -aq)

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