Tagged: Docker

Traefik, Portainer, Registry 를 위한 Docker Compose

docker-compose.yml 파일 내용은 다음과 같다.

위와같이 내용을 작성하고 YAML 파일로 저장한다. 저장한 파일 이름이 docker-frontend.yml 이라고 한다면 이것을 이용해서 다음과 같이 docker stack 을 deploy 한다.

stack 이 생성 정상적으로 생성되면 3개의 Service가 생성된다. 각각 Traefik, Portainer, Registry 이며 Portainer 는 Traefik 에 URI Reverse 로 연결된다. Portainer 의 URL 은 http://<IP>/portainer 이다.

Registry 에 .htpasswd 파일은 다음과 같이 생성하면 된다.

위 내용은 Traefik 를 앞에 두고 뒤에 Portainer 를 /portainer URI 로 접속하도록 한다. registry 서비스는 Traefik 와 별도로 작동되도록 하였다.

Docker 네트워크 이해

Docker 를 단순한 빠르고 가볍게 애플리케이션을 구성하게 해주는 컨테이너(Container) 로만 이해하는 건 좁은 시각이다. Docker 는 이러한 컨테이너들을 위한 네트워크 환경도 제공하고 있다. 이것은 Docker 가 종합적인 IT 인프라를 제공할 수 있다는 것을 의미한다.

Docker 네크워크 상태

Docker 를 호스트에 설치하면 우선적으로 다음과 같은 네트워크 장치가 하나 보인다.

컨테이너를 하나도 실행하지 않은 상태에서 단지 Docker 데몬이 실행만 되어도 이렇게 docker0 라는 네트워크 장치가 나타난다. 아이피는 172.17.0.1 이며 브로드캐스트(Broadcast) 가 172.17.255.255 인 것으로 봐서 docker0 장치가 속하는 네트워크 대역은 172.17.0.0/16 임을 알 수 있다.

docker0 는 Docker 가 실행되면서 자동으로 생성되는 가상의 네트워크 장치다. 네트워크 장치에도 여러가지 역할 혹은 모드를 부여할 수 있는데, docker0 가상네트워크는 브릿지모드로 동작하도록되어 있다.

docker0 가상네트워크가 브릿지 모드로 동작한다는 것은 이 브릿지에 연결한 네트워크 장비들을 외부와 통신하도록 해준다. 말 그대로 다리 역할을 하는 것이 docker0 이다.

이제 컨테이너 하나를 생성해 보자.

우분투(Ubuntu) 컨테이너 하나를 실행했다. 이 상태에서 호스트 네트워크를 살펴보면 다음과 같다.

없었던 veth83317a4 가 생겼다. 그리고 docker0 가상네트워크의 브릿지 상태는 다음과 같다.

docker0 에 veth83317a4 가상네트워크 장치가 연결되었다는 것을 볼 수 있다. 이것은 ‘ip link’ 나 ‘ip a’ 명령어로도 확인할 수 있다.

‘master docker0’ 가 이 가상네트워크 장치가 docker0 브릿지에 연결되었다는 것을 말해준다.

우분투 컨테이너의 네트워크 상태는 어떻게 될까? 다음과 같이 확인이 가능하다.

eth0 장치가 있고, 아이피는 172.17.0.2 로 할당되어 있다. 그리고 Default Gateway 는 다음과 같이 확인할 수 있다.

우분투 컨테이너의 Default Gateway 는 컨테이너를 실행한 호스트의 Docker 가상네트워크인 docker0 를 가르키고 있다. 결국 Docker 컨테이너의 모든 네트워크는 호스트의 docker0 를 가르키고 이는 모든 외부와의 트래픽은 docker0 을 통해서만 가능하다.

이를 도식으로 나타내면 다음과 같다.

Docker 컨테이너 네트워크

Docker0 브릿지 네트워크

이 docker0 브릿지 네트워크는 Docker 네트워크중에 하나다.

Docker 가 기본으로 제공하는 네트워크 기능들이 나온다. 여기서 bridge 에 대한 내용을 자세하 살펴보면 다음과 같다.

위 내용을 보면 IPAM 에 Config 를보면 네트워크 설정 내용이 보인다. 그리고 Options 에 “com.docker.network.bridge.name”: “docker0” 라는게 보인다. 이렇게 docker0 가상네트워크가 Docker 에서 생성한 Bridge 라는 것을 알 수 있다.

또, 현재 Bridge 에 연결되 실행되는 컨테이너 네트워크 정보도 함께 알 수 있다.

Docker 네트워크 특징

도커 호스트에 있는 컨테이너를 위한 가상네트워크 장비는 컨테이너와 peer 된 상태가 된다. 컨테이너를 하나씩 실행할때마다 하나씩 늘어난다. 컨테이너 갯수에 맞춰서 존재한다는 것이다.

도커 호스트에 컨테이너를 위한 가상네트워크 장비인 veth4327b92 는 컨테이너가 재시작될때마다 veth 를 제외한 숫자가 매번 바뀐다. 우분투 컨테이너를 재시작하고 난후 vethffc000e 로 변경됐다.

또, 위 도식에 보면 컨테이너 네트워크에 12라는 숫자가 보인다. 그리고 도커 호스트에 컨테이너를 위한 가상네트워크 번호가 13으로 나온다. 이를 보면 도커 컨테이너 네트크워크 숫자는 veth 가상호스트의 숫자보다 하나 작은 값을 가진다는 것을 알 수 있다. 이 숫자도 도커 컨테이너가 재시작될때마다 바뀌지만 1 작은 상태는 그대로 유지 된다. 이로써 도커 컨테이너 네트워크가 외부에 어느 veth 네트워크와 연결되는지를 알수 있게 된다.

또, 컨테이너에서 외부의 veth 가상네트워크를 볼 수가 없다. 이는 커널의 Network Namespace 로 격리를 하기 때문이다.

Docker Network Namespace

docker 의 Network Namespace 는 Docker 컨테이너가 실행될때마다 /var/run/docker/netns 디렉토리에 생성된다. 이에 대한 정보는 컨테이너 상세정보를 보면 알 수 있다.

이제 이것을 ip netns 에서 사용할 수 있도록 심볼릭 링크를 작성해 보자.

이렇게 한 후에 netns 를 살펴보면 다음과 같은 결과가 나온다.

이것을 이용하면 네트워크 정보를 확인할 수 있다.

Network Namespace 를 통해서 컨테이너에 네트워크 관련 명령어를 내릴 수 있다.

docker0 와 eth0 의 연결: iptables -t nat

docker0 에서 외부 네트워크 연결을 위해서 특별한 프로그램을 별도 운영하진 않는다. 단지, NAT 를 위한 Iptables 를 세팅한다. 이것은 오래전에 리눅스 박스를 라우터 장비로 이용하기 위한 방법인(지금에 비교를 하자면 리눅스 박스를 공유기로 이용하는 방법이다.) 마스커레이드(MASQUERADE) 과 완전 동일하다.

docker0 가상네트워크를 공유기로서 동작하도록 설정하는 것이다. iptables 를 한번 봐보자.

패킷이 docker 컨테이너로 들어올때는 PREROUTING 을 거치고 외부로 나갈때는 POSTROUTING 을 거치게 된다.

만일 iptables 명령어로 rule 를 무효화 하게 되면 docker 컨테이너는 외부와 통신하는게 불가능해 진다.

Inter-Container Communication

Docker 의 네트워크는 기본적으로 Inter-Container Communication 이다. 여러개의 컨테이너를 생성하면 각 컨테이너사이에 통신이 가능하다.

만일 이기능을 끄고 싶다면 docker 를 기동할 때에 다음과 같이 옵션을 주면 된다.

혹은 daemon.json 파일을 다음과 같이 작성해서 서비스를 재시작하는 방법도 있다.

이렇게 한 후에 컨테이너 사이에 통신은 불가능해 진다. 이것을 도커 컨테이너 네트워크 격리(Docker Container Network Isolation) 라고해서 구글 검색을 하면 꽤 많은 내용이 나온다.

왜 도커의 네트워크를 격리해야하는가? 이것을 잘 이용하면 네트워크 설계를 보다 보안성을 강화하면서 안전하게 설계할 수 있다. 이것은 마치 AWS 에서 VPC 설계시에 라우터를 조작해 네트워크를 구성하는것과 흡사하다.

이런것을 보면 Docker 는 단순한 컨테이너로서의 기능만을 위한게 아니라는 것을 알 수 있다. 네트워크 레이어 설계도 Docker 를 이해하는데 매우 중요한 요소다.

docker0 브릿지 네트워크 ip 변경

구글을 검색하면 많은 글들이 존재하는데, 최신의 docker 에서는 그렇게 많은 노력을 기울이지 않아도 docker0 브릿지 네트워크의 기본 ip 를 변경할 수 있다.

docker0 브릿지의 기본 네트워크 ip 정보를 변경하는 것은 서브넷, 게이트웨이등을 함께 변경하는 것을 뜻한다. 이는 /etc/docker/daemon.json 파일에서 bind ip 를 지정함으로써 쉽게 바꿀 수 있다.

이렇게 변경하고자하는 아이피를 지정하고 docker 를 다시 시작하면 된다. docker 를 재시작해야하는 일이기 때문에 많은 컨테이너가 운영중일때에 하기에는 문제가 있다.

따라서 이런경우에는 default 브릿지인 docker0 를 애써 변경하기 보다는 또다른 브릿지를 하나 생성하고 이것을 각각 컨테이너에 붙이는 방법으로 변경하는 것이 좋다.

사용자가 정의한 브릿지를 생성하는것을 사용자 정의 네트워크(User-Defined Network) 라고 한다.

Docker 에 Volumes 추가 하기.

Docker 를 서비스 모드로 실행을 시키면 나중에 문제가되는게 하나 있다. 예를들어, MySQL 을 Docker 를 이용해서 서비스 모드로 실행시켰다고 치자. 그러면 MySQL를 위한 데이터 디렉토리 Container 내에 생성된다. 만일 MySQL을 패치된 버전으로 올리고 싶다면 어떻게 될까? 이럴 경우에 데이터 디렉토리를 Container 내에서 백업할 방법이 없다.

이를 위해서 Docker 에는 Data Volumes 을 추가 할 수 있다.

Data Volumes

데이터 볼륨은 호스트 파일 시스템을 컨테이너에 마운트 하는 방법이다. 따라서 Container 가 중지되었거나 Container 를 삭제했다 하더라도 호스트 파일 시스템에는 Container 에서 사용했던 데이터 볼륨의 데이터가 살아 있게 된다.

이를 이용하면 MySQL, Redis 와 같이 데이터를 보존해야 하는 서비스를 위해서 호스트에 파일을 저장하도록 할 수있다.

-v 옵션을 통해서 로컬 호스트 디렉토리를 Container 의 데이터 디렉토리를, 여기서는 MySQL의 데이터 디렉토리인 /var/lib/mysql ,  마운트 하도록 했다.

이렇게 서비스를 실행하고 난 후에 호스트의 /home/systemv/mysql/data 디렉토리를 살펴보면 MySQL 위한 데이터 파일이 생성되었음을 확인 할 수 있다.

Docker inspect [Container]

이 명령어를 이용하면 Container 의 정보를 확인 할 수 있다. Data Volume 상태와 Container 의 IP 주소등을 다양한 정보를 살펴볼 수 있다.

Docker 실행하기

Docker 를 설치하고 나면 이제 실행을 해야 한다. 우선, 원래 실행 방법은 다음과 같은 순서를 따른다.

  1. Docker 이미지 다운로드
  2. 다운로드 된 Docker 이미지를 가지고 Container 생성
  3. Container 실행

Docker 이미지 다운로드는 pull 명령어를 이용하면 된다.

Docker ps -a

Docker 에서 실행중이거나 중지된 Container 의 목록을 보여 준다.

Docker create -t -i imageId /bin/bash [ –name naming ]

Docker Container 를 생성한다. -t 는 tty 를 할당하고 하게 -i 는 Interactive 를 말한다.  보통 -t, -i 옵션을 붙여서 -it 로 붙여서 쓴다. -i 옵션으로 인해서 STDIN 을 받을 수 있는데 이는 /bin/bash 를 통해서 받도록 한다.

Docker 를 생성하면 Container 는 Stopped 된 상태가 된다.

Docker start containerId

정지한 Container 를 start 한다.

Docker start containerId

실행중인 Container 를 stop 한다.

Docker attach containerId

현재 사용하고 있는 Terminal 에 STDIN, STDOUT, STDERROR 를 실행중인 Container 에 붙인다.

앞에서 생성한 Container 에 붙었다. 여기서 다음과 같이 한가지 재미있는 사실을 발견하게 된다.

Container 내에서 top 을 실행해 보면 bash 와 top 두개만 보인다. 원래 리눅스 머신이라면 커널 프로세스들이 모두 보여야 하지만 Container 는 그렇게 실행이 안된다는 사실을 보여준다.

정확하게 Container 가 어떻게 동작하는지를 보여준다.

Container 는 프로세스 격리 모두라는 사실을 보여주는 것이다. bash 를 기반으로 리눅스를 실행하고 Container 안에서 리눅스에서 실행된 명령어만 프로세스로 보인다.

exit 를 하면 Container 에서의 bash 가 종료 된다. 이렇게되면 Container 내에 프로세스가 동작하는게 하나도 없게되서 결국 Container 는 정지가 된다.

Dettach는 없다. 대신에 daemon mode 로 실행을 시켜주거나 잠시 Interactive 모드로 전환할 수는 있다. attach 된 상태에서 Ctrl + P + Q 를 하면 컨테이너를 빠져나오게 된다.

docker run [Options] imageId [Command]

Container 에 명령어를 실행하도록 한다. 만일 Image 가 없다면 Pull 하고 Container 를 Create 하고 Start 한 후에 명령어를 실행해 준다.

그래서 OS 같은 것을 실행하고 싶다면 다음과 같이 많이 사용한다.

 

서비스로 실행 시키기

서비스만 올릴 수 있다. 프로세스 격리모드로 동작하는 docker 에서는 OS를 구동할 필요가 없다. 그냥 서비스만 올릴 수 있다. Apache Httpd, Nginx, Redis, MySQL 등과 같이 외부와 통신을 하는 서비스들만 올리는게 가능하다.

서비스만 올리기에서 핵심은 외부와의 통신을 위한 방법을 제시해야 한다는 것이다. Redis 를 예를들어 보자.

위와같이 Redis 가 구동 되었다.

핵심이 두가지다. 서비스로 실행 시킬시에는 Bash 와같은 Interactive 를 할 수 없다. 따라서 Daemon 모드로 실해하기 위해서 -d 옵션을 준다. -p 로 포트 매핑을 해준다.

포트 매핑은 Docker Container 에서의 포트와 외부 노출되는 포트를 매핑해 주는 것으로 이는 브릿지 네트워크 모드를 이용할때 사용한다.

따라서 외부에서 접속을 하기위해서는 1234 포트를 이용해야 한다.

문제는 이러한 방법으로는 Redis 서비스를 설정할 수 없다. 각종 서비스를 운영할때에 그 서비스에서 사용가능한 옵션들을 설정할 수 있는 위 방법으로는 이게 불가능하다. 위 방법을 이용할 경우에는 기본 설정 값이 적용된다.

또, Redis 의 경우 데이터 저장소도 별도로 지정할 수가 없다. 그냥 모든게 기본 값이다.  Daemon 모드로 동작중이기 때문에 Container 를 중지하면 모든 데이터가 사라진다.

MySQL 올리기

MySQL 은 실행시에 Bash 쉘 환경변수를 인지해 값을 지정할 수 있다.

현시점에서 MySQL  의 최신버전은 5.7 이다. MySQL 5.7 은 처음 설치할때에 root 를 위한 임시패스워드를 생성하는데, Docker MySQL 을 생성할경우에 root 를 위한 임시패스워드를 생성하지 않도록 해야 한다.

MySQL 이 초기화가 어떻게 이루어졌는지를 보고 싶다면 docker logs containerId 를 해보면 된다.

 

참고: 초보를 위한 도커 안내서

Docker Hub

Docker Hub

Docker 는 컨테이너기반 애플리케이션 격리모드로 동작하도록 해준다. 기존의 Hypervisor 를 사용하는 가상화가 아닌 Docker Engine 이 각 애플리케이션을 격리해준다. 애플리케이션이라는 말은 운영체제를 포함한다.

이 컨테이너에서 운영할 각종 애플리케이션들이 있어야 하는데, 이런 것을 개개인이 만들어서 사용해야한다면 비용이 많이 들것이다. 예를들어 Ubuntu 16.04 를 Docker 에서 동작하기 위해서 Docker 컨테이너 환경에서 설치부터 해야한다면 가상화 시스템과 다를바가 없을 것이다.

그래서 많은 사람들이 사용할 애플리케이션을 미리 이미지로 구워서 필요한 애플리케이션이미지를 다운받아 각종 설정들과 함께 실행만 시키면 될 것이다. 이는 마치 리눅스 시스템에서 각종 애플리케이션을 컴파일 설치해야하는 대신에 미리 컴파일된 패키지를 이용하면 편리하다.

미리 컴파일된 패키지와 같은 것이 Docker Image 다. 미리 Docker 컨테이너 환경에서 동작할 수 있도록 미리 설치, 설정되어진 각종 애플리케이션을 말한다.

운영체제에서 미리 컴파일된 패키지를 제공하기 위해서 Redhat 에서는 Yum, Debian 에서는 apt 라는 공용 저장소를 제공한다. 내가 필요로하는 애플리케이션을 설치하고 싶다면  소스코드를 다운받아서 컴파일 설치할 필요없이 공용 저장소에 미리 컴파일된 패키지를 다운받아 설치만 하면 되는 것이다.

Docker Hub가 바로 위와 같다. Doker Hub 를 풀어서 말한다면 Docker Image 저장소쯤 될것이다. 필요로하는 애플리케이션들이 Docker Hub 에서  Image 를 다운받아서 구동만 해주면 바로 사용할 수 있게되는 것이다.

Docker Hub
Docker Hub

Docker Registry 라고 있는데, Docker 내부 컴포넌트중에 하나 인데 Docker Hub 에서 다운받아 가지고 있는 저장소라고 보면된다. 저장소이다보니까 공개된 Docker Hub 대신에 Private Hub 를 구축할때에 Docker Registry 를 사용기도 한다.

말을 잘해야 한다

여기서 컨테이너에서 동작하는 Image 라는 말은 잘못된 말이다. 거꾸로다. Image 를 가지고 컨테이너를 생성한다고 해야 맞는 말이다. 컨테이너는 가상환경이 아니다. 소프트웨어 격리 공간인데, 이 공간은 소프트웨어가 있어야만 가능하다. 따라서 Image 를 가지고 컨테이너를 생성한다고 해야 맞다.

docker search 이미지

이미지를 찾고 싶을때 사용한다. mariadb 로 검색을 하면 여러개가 나오는데 공식적으로 배포하는 이미지 말고는 전부다 커스텀 이미지라고 보면 된다.

docker pull 이미지:태그

이 명령어는 Docker Hub 에서 이미지를 다운받을 때 사용한다. 이미지를 Docker 서버에 다운로드 한다. 그런데, 이미지라도 버전이 있을 수 있다. 예를들어 mariadb 의 경우에도 다양한 버전이 존재한다. 이때 태그에 가능한 버전을 기입하면 그 버전을 다운받게 된다. 항상 최신의 버전을 다운받고 싶다면 latest 로 해주면 된다.

보통 태그는 그 소프트웨어의 버전인 경우가 많다.

doker images

Docker 서버에 다운받은 Image 리스트를 보여준다.

docker rmi 이미지ID

다운받은 Image 를 삭제한다. 인자로 이미지ID를 줘야 하는데, docker images 로 확인 가능하다.

 

Docker 기본 원리

Docker 는 Linux 의 Container 기술이다. 일단 Linux 에 해당하는 기술이라고 하는 이유와 함께 Container 를 위한 제반사항들에 대해서 알아본다.

과거에 Solaris 에 Zone 이라는 기술이 있었다. 가상화기술이 아니라 Container 기술이라고 소개가 되었던 기억이 있다. Solaris 9 에서 이전의 Solaris 8 을 Zone 이라는 기술을 이용해서 하나의 하드웨어에서 돌릴 수 있었다.

Linux 진영에서는 Jail 이라고 해서 chroot 로 파일시스템을 격리하는 기술을 이용해 마치 독립된 하나의 운영체제처럼 사용했었다. 지금도 검색을하면 Jail 환경에서 웹서버를 돌리는 기술 문서가 나온다.

그러다가 Linux 진영에서 아예 커널에서 이것을 지원하도록 만들기 시작했는데, 그것이 LXC 다. 내가 전에 직장에서 이것을 이용한 서비스를 만들던 친구녀석이 있었는데, 그때 나도 같이 봤던 기억이 있다. LXC 에서 cgroup 은 핵심이였다. 돌이켜 생각해보면 지금의 도커만큼이나 LXC 를 이용해서 구현을 할 수 있었던 것으로 기억한다. 하지만 노력과 비용이 너무 많이 들어갔다는게 문제였다.

Docker 는 정확하게 LXC 의 뼈대를 가졌다고 볼 수도 있다. 혹자는 최근의 Docker 는 LXC 를 버리고 libcontainer 를 독자적으로 사용하기 때문에 LXC 와 다르다고 하겠지만 LXC 가 사용하는 하부 구조를 Docker 도 그대로 사용한다 것을 부인할 수 없다.

  • Namespace
  • Cgroup

Docker 의 핵심은 위 두가지로 모두 Linux 커널에서 지원해야 한다. Docker 가 Linux 의 Container 기술이라고 하는 이유다. 실제로 Windows, Mac OS 에서도 설치할 수 있지만 설치할때 가상머신을 설치하고 난후에 Docker 를 설치하는걸 볼 수 있다.

Namespace

Namespace 는 마치 예전에 chroot 와 비슷하다. 다른 것이면 chroot 는 파일 시스템 격리를 우선했지만 Namespace 는 6가지를 지원한다.

  • File System
  • Process
  • Network
  • IPC
  • Hostname
  • User

Namespace 는 프로그래밍 세계에서도 사용하는 용어인데,  그곳에서 보여주는 특성과 동일하다. 6가지에 대해서 격리된 공간(isolated space)를 만들어준다. 운영체제는 분명 1개인데, 여러개의 격리된 독립적인 공간을 가질 수 있다.

물론 이를 위해서는 커널에서도 지원해야 한다.

Cgroup

Cgroup 은 Linux 시스템의 자원(Resource) 를 제어할 수 있게 해준다. 자원에 대해서 그룹을 생성한다. 그리고 그 그룹을 특정 사용자가 사용할 수 있도록 소유권을 준다. 그러면 사용자가 프로그램을 실행할때 cgroup 을 할당하면 거기에 있는 자원만큼만 사용하게 된다.

다음과 같은 자원을 제어할 수 있다.

  • Memory
  • CPU
  • I/O
  • Network
  • Device

당연히 이것도 커널에서 지원해야 한다.  커널이 지원하면 다음과 같이 /sys 에 cgroup이 나타난다.

 

애초에 Docker 도 LXC 를 이용해 구현되었다가 지금은 runC 라고 하는 걸 독자적으로 만들어서 사용하고 있다고 한다.

참고:

  1. Docker(container)의 작동 원리
  2. 초보를 위한 도커 안내서 – 도커란 무엇인가?