본문 바로가기

클라우드/도커

컨테이너 실전 구축 및 배포

반응형

도커를 사용하여 시스템을 구성한다는 것은 자신이 만든 애플리케이션 컨테이너와 도커 허브에 공개된 애플리케이션이나 미들웨어 이미지로 만든 컨테이너가 서로 협력하는 스택을 구축하는 것이다.

실제 운영에서는 애플리케이션을 컨테이너 안에 어떻게 배치하는지가 매우 중요하다. 컨테이너 하나가 맡을 수 있는 적정 수준의 책임 등 다양한 측면을 고려해야한다.

1. 컨테이너 1개 = 프로세스 1개?

도커는 애플리케이션 배포에 특화된 가상화 기술이기 때문에 웹 애플리케이션과 상주 애플리케이션 프로세스 하나를 하나의 컨테이너로 만드는 방식이 괜찮게 생각될 수 있다.

만약, 정기적으로 어떤 작업을 실행하는 컨테이너의 경우 스케줄러와 작업이 합쳐진 애플리케이션을 만든다면 컨테이너 1개 = 프로세스 1개 원칙을 지킬 수 있다. 그러나 스케줄러를 직접 갖춘 애플리케이션만 있는 것은 아니다. 대부분은 스케줄러를 외부 기능에 의존한다.

스케줄러 기능이 없는 애플리케이션을 사용해 정기 작업을 실행하려면 cron을 사용하는 것이 자연스럽다. cron은 1개의 상주 프로세스 형태로 동작하는데, 이 스케줄러가 실행하는 작업 역시 하나의 프로세스로 동작한다. 컨테이너 1개 = 프로세스 1개 방식을 택한다면,  cron이 1개 컨테이너고 실행되는 작업이 또 1개의 컨테이너를 차지하는 형태로 구성해야 한다. 이를 구현하기 위해서는 아래와 같은 구조가 필요한데, 이는 지나치게 복잡하다. 

  • 작업 컨테이너 쪽에 작업을 실행하는 트리거 역할을 할 api 를 갖추고, cron 컨테이너가 컨테이너 간 통신을 통해 api를 호출하는 구조
  • cron 컨테이너에 도커를 구축하고 다시 그 위에서 작업 컨테이너를 실행하는 구조

우선 cron으로 실행할 쉘 파일 하나를 작성해보자.

#!/bin/sh
echo "[`date`] Hello" >> /var/log/cron.log

cron 정의는 아래와 같이 정의하여 1분에 한 번씩 실행되도록 한다.

* * * * * root sh /usr/local/bin/task.sh

마지막으로 Dockerfile은 아래와 같이 작성한다.

FROM ubuntu:16.04

RUN apt update
RUN apt install -y cron

COPY task.sh /usr/local/bin/
COPY cron-example /etc/cron.d/
RUN chmod 0644 /etc/cron.d/cron-example

CMD ["cron", "-f"]

ubuntu:16.04 이미지를 사용하고 여기에 패키지 관리자 apt를 사용해 cron을 설치한다. task.sh와 cron 정의 파일을 컨테이너로 복사하고 cron 정의 파일의 권한을 644로 조정한다.

그 다음, CMD 인스트럭션에서 cron을 실행한다. cron은 백그라운드에서 동작하는 프로세스이므로 이렇게 CMD 인스트럭션에서 실행하면컨테이너가 바로 종료되기 때문에 -f 옵션을 붙여 포어그라운드에서 실행되도록 한다.

이 상태에서 도커 이미지를 빌드한다.

그 다음, 빌드된 이미지를 실행한다.

실행 중인 컨테이너의 로그 파일을 보면 출려한 무자열이 1분마다 추가되고 있음을 알 수 있다.

이런 경우는 간단한 편에 속하지만, 이런 비슷한 상황을 고려하였을 때 컨테이너 1개 = 프로세스 1개 원칙을 고수하는 것은 비현실적이다. 도커를 사용하는 이유가 편의성을 위한 것인데, 이쯤 되면 앞뒤가 바뀐 것이라 할 수 있다.

컨테이너가 하나의 프로세스만 포함한다기보다는 한 가지 역할이나 문제 영역(도메인)에 집중하는 것이 좋다. 전통적인 웹 어플리케이션 스택을 생각했을 때, 컨테이너를 사용하지 않는 스택에서는 리버스 프록시, 애플리케이션, 데이터 스토어가 서로 독립적으로 자기 역할을 수행하며 전체시스템을 구성한다. 이런 분리 구조라면 그대로 컨테이너로 바꿔도 어색하지 않다.

정리하면, "각 컨테이너가 맡은 역할을 적절히 나누고, 그 역할에 따라 배치한 컨테이너를 복제해도 전체 구조에서 부작요이 일어나지 않는가?"를 따져가며 시스템을 설계하는 것이 좋다.

 

2. 컨테이너의 이식성

도커의 큰 장점은 이식성이다. 도커를 사용하면 애플리케이션과 인프라를 컨테이너라는 단위로 분리할 수 있으며, 도커가 설치된 환경이라면 어떤 호스트 운영체제와 플랫폼, 온프레미스 및 클라우드 환경에서도 그대로 동작한다.

여기서 도커의 이식성의 몇가지 예외가 존재한다.

2-1. 커널 및 아키텍처의 차이

도커에서 사용되는 컨테이너형 가상화 기술은 호스트 운영체제와 커널 리소스를 공유한다. 이는 도커 컨테이너를 실행하려면 호스트가 특정 CPU 아키텍처 혹은 운영 체제를 사용해야 한다는 의미이다. 보통 centOS나 우분투, 현재 공식 이미지의 사실상 표준 역할을 하는 알파인 리눅스를 기반으로 도커 이미지를 빌드하거나 사용하지만, 이 이미지는 어디까지나 x86_64 아키텍처에서 실행하는 것으로 만들어진 것이며 다른 아키텍처에서 동작하는 도커에서는 실행이 보장되지 않는다.

2-2. 라이브러리와 동적 링크 문제

애플리케이션이 어떤 라이브러리를 사용하느냐에 따라 이식성을 해칠 수 있다. 정적 링크는 애플리케이션에 사용된 라이브러리를 내부에 포함하는 형태이므로 애플리케이션의 크기가 비대해지는 경향이 있지만 이식성이 뛰어나다. 이에 비해 동적 링크는 애플리케이션을 실행할 때, 라이브러리가 링크되므로 애플리케이션 크기는 작아지지만 애플리케이션을 실행할 호스트에서 라이브러리를 갖추어야 한다.

도커를 사용할 때, ADD 혹은 COPY 인스트럭션처럼 파일을 복사하는 기능을 사용하여 외부에서 애플리케이션을 주입할 수 있다. CI 시간을 줄이기 위해 컨테이너 안에서 빌드하기 보다는 빌드된 애플리케이션을 그대로 컨테이너에 복사하는 경우가 많은데 이런 경우에 동적 링크 문제가 생긴다.

위와 같은 동적 링크 문제를 피하려면 모든 의존 라이브러리를 정적으로 링크해 상요하거나 플랫폼에도 같은 라이브러리를 설치해야 한다. 예를 들어, 표준 C 라이브러리를 매번 CI 서비스에 맞춰 바꾸는 것은 효율적이지 못하다. 그렇다고 정적 링크를 사용하게 되면 실행 파일의 크기가 커지는 단점이 생긴다. 이러한 단점들 마저 피하기 위해서는 모든 빌드 프로세스를 실행하는 도커 컨테이너를 만들고, 그 안에서 애플리케이션을 빌드하면 동일 라이브러리를 사용하는 것이 보장되므로 이를 다른 컨테이너에서 사용해도 문제가 없을 것이다.

 

3. 도커 친화적인 애플리케이션 

이식성이 높은 애플리케이션을 구축하기 위해 필요한 요소를 생각해본다.

애플리케이션을 만들 때는 일반적으로 재사용성과 유연성을 가질 수 있도록 옵션을 만들어 두고, 이를 옵션에 따라 애플리케이션의 동작을 제어한다. 동작을 제어하는 방법은 다음과 같이 네 가지 방법을 예로 들 수 있다.

1. 실행 시 인자 사용

외부에서 실행 시 인자 형태로 값을 전달할 때, 인자 갯수가 너무 많아지면 애플리케이션에서 읹를 내부 변수로 맵핑해주는 처리가 복잡해지거나 CMD 및 ENTRYPOINT 인스트럭션 내용을 관리하기 어렵다.

2. 설정 파일 사용

매우 널리 사용되는 방식이다. 그러나, 특정 환경에 대한 설정 파일을 도커 이미지에 포함시키면 이식성을 해치게 된다. 완전히 제한되는 특정 환경이 아니라 다른 환경에서 애플리케이션을 실행하려면 컨테이너에 설정 파일을 추가해야 하므로 그 때마다 이미지를 새로 빌드해줘야 한다.

3. 애플리케이션 동작을 환경 변수로 제어

앞서 젠킨스 예제에서 슬레이브 쪽에 JENKINS_SLAVE_SSH_PUBKEY 라는 환경 변수에 값을 설정하는 방식과 같다. 환경 변수를 사용하게 되면 매번 이미지를 다시 빌드하지 않아도 된다는 장점이 있다. 환경 변수는 컴포즈를 사용하는 경우 docker-compose.yml 파일의 env 속성에 기술해 관리한다.

4. 설정 파일에 환경 변수를 포함

도커에서는 1-3. 방법을 제일 추천한다. 그러나 설정 파일의 장점을 포기하기 어려운 경우가 있다. 설저 파일에 환경 변수를 포함할 수 있다면 환경 변수의 장점과 설정 파일의 장점을 모두 취할 수 있다.

 

4. 퍼시스턴스 데이터를 다루는 방법

만약, 컨테이너가 일정 상태를 갖는(stateful) 한 유형이라면 컨테이너가 파기 되었을 때 완전히 동일하게 재현하기 어렵다. 새로운 버전의 컨테이너가 배포되어도 이전 버전의 컨테이너에서 사용되던 파일 및 디렉터리를 그대로 이어받아 사용할 수 있어야 한다. 

이럴 경우 데이터 볼륨이 사용되는데, 이는 도커 컨테이너 안의 디렉터리를 디스크에 퍼시스턴트 데잍로 남기기 위한 메커니즘으로, 호스트와 컨테이너 사이의 디렉터리 공유 및 재사용 기능을 제공한다. 

우선, 컨테이너에서 생성된 파일을 호스트에서 참조하는 경우를 예로 들 수 있다.

위 명령의 convert 이후 부분이 컨테이너에 전달될 애플리케이션 실행 인자이다. 그 내용은 100*100  크기의 흑백 이미지를 담은 사진 파일 /workspace/gihyo.jpg 을 생성하라는 명령이다.

-v 옵션을 사용해 데이터 볼륨이 설정돼 있으므로 컨테이너 안의 /workspace 디렉터리는 환경변수 $PWD가 나타내는 디렉터리에 마운트된다. 

현재 작업 디렉터리 아래 gihyo.jpg 파일이 공유되어 있는 것을 볼 수 있다. 데이터 볼륨은 공유 기능을 제공하므로 호스트에서 편집한 파일을 데이터 볼륨을 통해 이미지를 수정하지 않고도 컨테이너와 공유할 수 있다.

데이터 볼륨 컨테이너

데이터 볼륨 컨테이너는 컨테이너 간에 디렉터리를 공유한다. 데이터 볼륨 컨테이너는 말 그대로 데이터를 저장하는 것만이 목적인 컨테이너이다.

MySQL을 예로 데이터 볼륨 컨테이너를 사용해 보자. 데이터 볼륨 컨테이너 역할을 할 이미지를 다음과 같은 Dockerfile로 생성한다.

FROM busybox

VOLUME /var/lib/mysql

CMD ["bin/true"]

이 이미지의 컨테이너를 mysql-data라느 이름으로 데이터 볼륨 컨테이너로 실행한다. 

이어서 MySQL을 동작시킬 컨테이너를 실행한다. --volumes-from 옵션을 사용해 데이터 볼륨 컨테이너 mysql-data를 MySQL 컨테이너에 마운트 한다. 이제 MySQL 컨테이너의 /var/lib/mysql 에는 데이터가 저장되지 않는다.

docker container run -d --rm --name mysql \
-e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" \
-e "MYSQL_DATABASE=volume_test" \
-e "MYSQL_USER=example" \
-e "MYSQL_PASSWORD=example" \
--volumes-from mysql-data \
mysql:5.7

실행 중인 mysql 컨테이너에 root 계정으로 로그인하여 초기데이터로 다음과 같은 CREATE, INSERT 쿼리를 보낸다. (초기 패쓰워드는 빈 문자열)

docker container exec -it mysql mysql -u root -p volume_test
CREATE TABLE USER(
	id int PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;

INSERT INTO USER(name) VALUES ('bobo'), ('docker'), ('Solomon');

위와 같이 데이터가 잘 들어간 것을 확인할 수 있다.

이제 실행중인 mysql 컨테이너를 멈추고 다시 재 실행 시켜보면 데이터가 잘 남아있는 것을 확인할 수 있다.

 

만약, 사용하던 데이터를 다른 도커 호스트로 이전하기 위해서는 데이터 볼륨 컨테이너에서 원하는 데이터를 파일로 익스포트 한 뒤 그 파일을 호스트로 꺼내야 한다. 

busybox 컨테이너를 새로 실행한 후, 그 다음 데이터 볼륨 컨테이너를 mysql-data로 지정한다. 컨테이너 안에서 tar로 데이터를 압축한 다음, 압축된 파일이 위치한 /tmp 디렉터리를 현재 작업 디렉터리에 마운트한다.

docker container run -v ${PWD}:/tmp \
> --volumes-from mysql-data \
> busybox \
> tar cvzf /tmp/mysql-backup.tar.gz /var/lib/mysql

다른 도커 호스트에 이 데이터를 옮기려면 새로운 데이터 볼륨 컨테이너를 만들고 이 컨테이너 안에 조금 전 만든 압축 파일을 풀어주면 된다.

반응형

'클라우드 > 도커' 카테고리의 다른 글

서비스와 스택  (0) 2021.11.29
도커 스웜  (0) 2021.11.28
도커 컴포즈로 여러 컨테이너 실행하기  (0) 2021.10.30
도커 컨테이너  (0) 2021.10.17
도커 이미지와 컨테이너 기본 개념과 실행  (0) 2021.10.17