리눅스 컨테이너는 운영체제 수준의 가상화 기술로 리눅스 커널을 공유하면서 프로세스를 격리된 환경에서 실행하는 기술입니다.
하드웨어를 가상화하는 가상머신과 달리 커널을 공유하는 방식이기 때문에 실행 속도가 빠르고, 성능 상의 손실이 거의 없습니다.
컨테이너로 실행된 프로세스는 커널을 공유하지만, 리눅스 네임스페이스, 컨트롤 그룹, 루트 디렉토리 격리 등의 커널 기능을 활용해 격리되어 실행됩니다. 이러한 격리 기술 덕분에 호스트 머신에게는 프로세스로 인식되지만, 컨테이너 관점에서는 마치 독립적인 환경을 가진 가상 머신처럼 보입니다.
(좌) 도커 컨테이너, (우) 가상머신
컨테이너는 운영체제 수준의 가상화 기술입니다. 별도의 하드웨어 에뮬레이션 없이 리눅스 커널을 공유해 컨테이너를 실행하며, 게스트OS 관리가 필요하지 않습니다.
하드웨어 에뮬레이션이 없기 때문에 컨테이너는 아주 빠르게 실행됩니다. 프로세스 격리를 위해 아주 약간의 오버헤드가 있지만 일반적인 프로세스를 실행하는 것과 거의 차이가 없습니다. 또한 하나의 머신에서 프로세스만큼 많이 실행하는 것이 가능합니다.
모든 컨테이너는 호스트의 환경이 아닌 독자적인 실행 환경을 가지고 있습니다. 이 환경은 파일들로 구성되며, 이미지 형식으로 공유될 수 있습니다. 리눅스 커널을 사용하고 같은 컨테이너 런타임을 사용할 경우 컨테이너의 실행 환경을 공유하고 손쉽게 재현할 수 있습니다.
컨테이너가 실행되는 환경은 독립적이기 때문에, 다른 컨테이너에게 영향을 주지 않습니다. 도커와 같이 이미지 기반으로 컨테이너를 실행하는 경우 특정 실행 환경을 쉽게 재사용할 수 있습니다.
시스템 컨테이너는 컨테이너 기술들을 사용해 운영체제 위에 하드웨어 가상화 없이 운영체제를 실행합니다. 일반적인 리눅스처럼 init 프로세스 등을 사용해서 다수의 프로세스가 같은 환경을 공유하는 것을 목표로 합니다. 시스템 컨테이너를 지향하는 컨테이너 런타임으로 대표적으로 LXC, LXD가 있습니다.
컨테이너 기술을 활용해 하나의 애플리케이션을 실행하는 것을 목표로 합니다. 독립적인 환경을 가진다는 점에서는 시스템 컨테이너와 동일하지만, 단 하나의 프로세스만 실행한다는 점에서 확장이 쉽고 관리 요소가 거의 없습니다. 대표적인 애플리케이션 컨테이너 런타임으로는 도커가 있습니다.
2014년 애플리케이션 컨테이너 런타임인 도커가 등장하면서 컨테이너 기술이 많은 관심을 받고 있습니다. 서버 운영에서 컨테이너는 서버 애플리케이션의 배포 단위를 새로 정의했고, 인프라스트럭처와 클라우드 변화에 큰 변화를 가져왔습니다.
기존의 서버 운영에서는 애플리케이션을 실행하기 위해 서버 컴퓨터의 상태를 지속적으로 관리해야했지만, 컨테이너를 사용하면 애플리케이션 별도 독자적인 환경을 준비하고 관리하는 것이 가능하기 때문에 서버 컴퓨터를 관리할 필요가 적어진다는 장점이 있습니다. 이러한 장점으로 컨테이너 기술의 활용은 2020년 현재 급속하게 확산하고 있습니다.
컨테이너는 쉽게 말하자면 애플리케이션을 환경에 구애 받지 않고 실행하는 기술입니다.
가령 gitlab이라는 툴을 설치하려면 우분투에서는 아래 코드를
sudo apt-get update
sudo apt-get install -y curl openssh-server ca-certificates
sudo apt-get install -y postfix
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh | sudo bash
sudo EXTERNAL_URL="http://gitlab.example.com" apt-get install gitlab-ee
centos에서는 아래 코드를 사용합니다.
sudo yum install -y curl policycoreutils-python openssh-server
sudo systemctl enable sshd
sudo systemctl start sshd
sudo firewall-cmd --permanent --add-service=http
sudo systemctl reload firewalld
sudo yum install postfix
sudo systemctl enable postfix
sudo systemctl start postfix
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash
sudo EXTERNAL_URL="http://gitlab.example.com" yum install -y gitlab-ee
즉, 운영체제에 따라 명령어가 다릅니다.
그러나 컨테이너 도구인 도커가 설치되어 있다면 어느 환경이든 상관없이 다음 명령어를 사용하여 깃랩을 실행할 수 있습니다.
$ docker run --detach \
--hostname gitlab.example.com \
--publish 443:443 --publish 80:80 --publish 22:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
즉, 운영체제별로 존재하는 복잡한 설치과정을 겪지 않습니다.
이렇듯 편리함과 확장성을 지닌 컨테이너가 서버 세상을 많이 변화시켰습니다.
도커로 애플리케이션을 배포하고 운영한다는 것은 서버를 코드로 구성하고 관리할 수 있다는 것입니다.
서버를 코드로 구성하고 관리하는 방법이 도커의 큰 장점입니다.
컨테이너 기술은 k8s 등 다양하지만 가장 많이 알려진 도커로 설명하겠습니다.
서버를 운영하다보면 서버가 마음대로 작동하지 않을 때가 있습니다.
서버 기술과 별개로 서버마다 운영 기록이 다르기 때문입니다. 똑같은 일을 하는 두 서버가 있다고 해도 A 서버는 한달전에 구성했고, B 서버는 이제 막 구성했다면 운영체제부터 컴파일러, 설치된 패키지까지 완벽하게 같기는 쉽지 않습니다. 이러한 차이점들이 장애를 일으키구요...
A 서버는 잘 되는데 B 서버는 왜 죽었지? 와 같은 일이 벌어지는 겁니다.
이렇게 서로 모양이 다른 서버들이 존재하는 상황을 눈송이 서버라고 합니다.
A, B 서버에 어떠한 툴을 설치하려고 하는데 A에서만 장애가 발생하여 원인을 규명하려고 하는데 정확하고 빠른 규명을 위해서 설치했던 사람이 필요한데 그 분이 퇴사했거나 없을 수 있죠. 또한 오래 서버를 운영하다보면 레거시가 늘어나니 차이점이 걷잡을 수 없이 커집니다..
이러한 상황을 개선하고자 다양한 방식으로 서버 운영 기록을 저장해 두곤 합니다.
가장 흔하게 서버에서 어떤 작업을 실행할 때마다 사내 문서 도구에 기록해둔다거나, 여러 서버에 동시 접속해서 한꺼번에 명령을 실행하는 도구를 사용하기도 하죠.
하지만, 문서에 적힌대로 해봐도 안되는 경우가 많고... 여러 서버를 한번에 조작할 때 특정 서버 한대만 말썽인 경우도 허다합니다..
따라서 서버의 운영 기록을 코드화하려는 다양한 시도가 등장합니다.
대표적인 anisble로 이미지매직을 설치하는 코드를 살펴봅시다.
# imagemagick를 설치하는 Ansible 플레이북
# code by https://github.com/treetips/ansible-playbook-imagemagick/blob/master/main.yml
---
# ansible v2.0.0.2
- hosts: all
become: no
vars:
- autoconf_dir_name: "autoconf-latest"
- autoconf_archive_name: "{{ autoconf_dir_name }}.tar.gz"
- autoconf_dl_url: "http://ftp.gnu.org/gnu/autoconf/{{ autoconf_archive_name }}"
- imageMagick_dir_name: "ImageMagick-latest"
- imageMagick_archive_name: "ImageMagick.tar.gz"
- imageMagick_dl_url: "http://www.imagemagick.org/download/{{ imageMagick_archive_name }}"
tasks:
(중략)
- block:
- name: downlaod imagemagick
get_url: url={{ imageMagick_dl_url }} dest=/usr/local/src/{{ imageMagick_archive_name }}
- name: unarchive imagemagick archive
shell: chdir=/usr/local/src mkdir -p {{ imageMagick_dir_name }} && tar xzvf {{ imageMagick_archive_name }} -C {{ imageMagick_dir_name }} --strip-components 1
# see configure options http://www.imagemagick.org/script/advanced-unix-installation.php
- name: configure imagemagick
shell: chdir=/usr/local/src/{{ imageMagick_dir_name }} ./configure
- name: make imagemagick
shell: chdir=/usr/local/src/{{ imageMagick_dir_name }} make
- name: make imagemagick
shell: chdir=/usr/local/src/{{ imageMagick_dir_name }} make install
when: imagemagick_archive.stat.exists == False
become: yes
도커에서 사용하는 도커파일도 앞서 이야기한 서버 운영 기록을 코드화한 것입니다.
# Nginx 서버를 구성하는 도커 파일
FROM debian:stretch-slim
RUN apt-get update \
&& apt-get install -y \
imagemagick
이 도커 파일로 도커 이미지를 만들 수 있습니다.
도커 파일이 서버 운영 기록이라면, 도커 이미지는 운영 기록을 실행할 시점이라고 할 수 있습니다.
도커 파일 = 서버 운영 기록 코드화
도커 이미지 = 도커 파일 + 실행 시점
예를 들어 앞서 살펴본 앤서블의 플레이북으로 서버에 이미지매직을 설치한다고 합시다.
작업자가 1년 전에 이 플레이북으로 서버 A를 구성했고, 오늘 서버 B를 구성한다면, 두 서버에 대해 이미지매직이 설치된 시점은 1년의 차이가 있습니다.
하지만 도커에서는 앞서 살펴본 도커 파일로 이미지를 만들어 두면, 서버가 구성되는 시점이 이미지를 만든 시점으로 고정됩니다.
이 이미지를 사용해 1년 전에 A 서버에 컨테이너를 배포하고, 오늘 B서버에 컨테이너를 배포한다고 해도 두 컨테이너 모두 이미지매직이 설치된 시점은 같습니다.
이것이 바로 도커가 여느 서버 구성 도구와 가장 다른 부분입니다.
다른 도구들은 모두 도구를 실행하는 시점에 서버의 상태가 결정되는 데 비해, 도커는 작업자가 그 시점을 미리 정해둘 수 있습니다.
덕분에 서버를 항상 똑같은 상태로 만들 수 있는 것이죠.
한편, 도커를 처음 접한 분들이 불편하게 여기는 부분 중 하나가 바로 도커파일인데요.
명령어가 어려워서라기보다는 한번 작성해서 이미지 빌드까지 성공하는 경우가 드물다보니, 수정해서 다시 빌드하는 과정을 반복해야 하기 때문입니다. 더군다나 빌드하는 시간이 점점 길어지는데 그러다보면 마치 코드 작성 후 컴파일하는 시간처럼 느껴지기도 하죠...
이는 다르게 도커의 장점일 수 있습니다.
소프트웨어 작성에 도움을 주는 기법 중 하나인 테스트 주도 개발은 잘 아실겁니다.
모르신다면, 여기를 보시면 잘 이해되실거에요~~
TDD 순환도
도커파일 역시 이런 식으로 생각해볼 수 있습니다.
1. 도커 파일을 만들고
2. 도커 이미지 만들기에 실패하고,
3. 도커 파일을 작성/수정한 후,
4. 도커 이미지 만드는 데 성공합니다.
5. 필요 없는 부분은 지우고 합칠 수 있는 명령은 합칩니다. (=효율화)
6. 1번으로 돌아갑니다.
도커파일 작성 순환도
서버를 만들 때는 미리 실패해서 보완하는게 매우 중요합니다. 왜냐하면 지금 미리 겪은 실패는 약간의 기다림과 귀찮음 뿐이지만, 지금 겪지 않은 실패는 훗날 서비스 장애로 이어지기 때문입니다.
서비스 장애 없이 서버를 수정할 수 있다는 도커의 장점과 TDD가 주는 안정감이 서로 맥락이 닿아 있다고 생각합니다.
이해를 위해 이미지매직 설치용 도커파일을 만들어봅시다.
(우분투 기반의 이미지)
# Dockerfile
FROM ubuntu:latest
RUN apt install imagemagick
이제 이미지를 빌드해봅니다.
$ docker build -t my-imagemagick .
Sending build context to Docker daemon 23.26MB
Step 1/2 : FROM ubuntu:latest
---> 93fd78260bd1
Step 2/2 : RUN apt install imagemagick
---> Running in 81d55446049c
Reading package lists...
Building dependency tree...
Reading state information...
E: Unable to locate package imagemagick
The command '/bin/sh -c apt-get install imagemagick' returned a non-zero code: 100
이미지매직 패키지를 찾지 못해서 실패했습니다.
패키지 목록을 업데이트 하지 않았기 때문에 난 에러로 업데이트 문을 추가합니다.
# Dockerfile
FROM ubuntu:latest
RUN apt update
RUN apt install imagemagick
다시 빌드해봅니다.
$ docker build -t my-imagemagick .
Sending build context to Docker daemon 23.26MB
Step 1/3 : FROM ubuntu:latest
---> 93fd78260bd1
Step 2/3 : RUN apt update
---> Running in b7d442841e90
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [83.2 kB]
... (중략) ...
Reading package lists...
Building dependency tree...
Reading state information...
4 packages can be upgraded. Run 'apt list --upgradable' to see them.
Removing intermediate container b7d442841e90
---> 5345ed18a95b
Step 3/3 : RUN apt install imagemagick
---> Running in f3cb19701d3f
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Reading package lists...
Building dependency tree...
Reading state information...
The following additional packages will be installed:
dbus fontconfig fontconfig-config fonts-dejavu-core fonts-droid-fallback
... (중략) ...
Need to get 36.3 MB of archives.
After this operation, 135 MB of additional disk space will be used.
Do you want to continue? [Y/n] Abort.
The command '/bin/sh -c apt install imagemagick' returned a non-zero code: 1
패키지를 설치할지 여부를 묻고 있는데 입력을 해주지 않았기 때문에 에러코드와 함께 셸이 종료되었습니다. 이를 해결하고자 apt install 부분에 -y 옵션을 추가합니다.
# Dockerfile
FROM ubuntu:latest
RUN apt update
RUN apt install -y imagemagick
이제 다시 빌드합니다.
$ docker build -t my-imagemagick .
... (중략) ...
Successfully tagged my-imagemagick:latest
이미지가 잘 만들어집니다. 이렇게 명령어를 추가하고 -> 실패하고 -> 수정하는 과정을 통해 도커 파일을 완성할 수 있습니다.
마지막 과정에서 이미지를 만드는 데 성공했으므로, 이 이미지로 서버를 실행해봅시다.
$ docker run -d my-imagemagick
... (중략) ...
도커 이미지로 서버를 실행하면 도커 컨테이너가 만들어집니다.
앞서 이야기했지만, 컨테이너를 언제 실행하든 이미지가 변하지 않았다면 컨테이너의 내용도 완전히 똑같습니다. 드디어 배포(혹은 실행)하는 시점과 상관 없이 서버의 내용을 똑같게 만들려는 우리의 노력이 성공한 듯 합니다.
도커 이미지로는 언제든 똑같은 형태의 서버를 실행(=도커 컨테이너)할 수 있습니다. 그런데 코드나 도커 파일을 전혀 수정하지 않은 채 내일 도커 이미지를 빌드하면 어떨까요? 서버가 똑같기를 기대하겠지만 실제로는 달라질 수도 있습니다. 왜냐하면 서버에 설치하는 패키지가 보안 문제를 겪어서 하루 사이에 패치되었거나 할 수 있기 때문입니다. 그러면 도커를 사용하는 장점이 퇴색되는 걸까요?
지금까지 서버를 똑같이 만드는 데 노력을 기울였지만, 사실 서버에는 바뀌어야 할 부분도 있습니다. 일례로 한 컴퓨터에서 A라는 도커 컨테이너를 두 개 배포했다면, 이 둘을 어떻게 구분할까요? 도커에서는 내부 규칙에 따라 해시 값(=컨테이너 id)과 임의의 이름(=컨테이너 이름)을 붙입니다. 물론 IP도 다르고요.
도커에서는 이렇게 바뀌어야 할 부분을 환경변수에 넣고 관리하도록 권합니다. 이러한 도커 이미지의 특징은, 소프트웨어 분야의 클래스와 public 변수, private 변수에 비견할 수 있습니다.
도커 파일에 실행 시점을 더한 것이 도커 이미지라면, 도커 이미지에 실행 시점이 수정되어야 할 정보들을 더한 것이 도커 컨테이너입니다.
도커 파일 == 서버 운영 기록
도커 이미지 == 도커 파일 + 실행 시점
도커 컨테이너 == 도커 이미지 + 환경 변수
도커(정확히는 컨테이너 기술) 덕분에 서버를 설치하고 운영 기록을 별도로 관리하는 고단함 없이, 잘 만들어진 서버를 사용할 수 있습니다. 심지어 내가 만든 서버가 아니어도 말이죠
지금까지 살펴본 내용이 모두 서버 코드화가 가져다 준 장점입니다.
참조 : https://www.44bits.io/ko/post/why-should-i-use-docker-container
https://www.44bits.io/ko/keyword/linux-container