Gitlab 에서 사용할 opentofu 도커 이미지를 제작해보자. Gitlab 에서 Auto DevOps 를 이용할 경우에 Docker 이미지를 이용해서 Terraform 빌드, 배포 하는게 가능하다. 지금은 Terraform 을 위한 Docker 이미지만 존재하는데, Opentofu 를 위한 도커 이미지를 제작해서 사용할 수 있도록 이미지를 만들어 보자.
Dockerfile
Docker 빌드를 위해서 Dockerfile 이 있어야 한다. 다음과 같다.
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ARG BASE_IMAGE
FROM$BASE_IMAGE
RUN apk add--no-cache\
curl\
gcompat\
git\
idn2-utils\
jq\
openssh
# Install tofu from the alpine registry and symlink to terraform (for the utility script)
RUN apk add--no-cache opentofu--repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing/ && \
ln-s"$(which tofu)"/usr/local/bin/terraform&&\
terraform--version&&\
tofu--version
WORKDIR/
COPY gitlab-terraform.sh/usr/bin/gitlab-terraform
RUN chmod+x/usr/bin/gitlab-terraform
# Override ENTRYPOINT
ENTRYPOINT[]
내용을 보면 gitlab-terraform.sh 스크립트 파일이 있어야 한다. 이 파일은 실제 Gitlab 을 이용해서 CI/CD 를 할 경우에 실제로 사용하게 되는 스크립트 파일이다.
Docker 서버를 구축했으며, 도메인은 정식 도메인이 아닌 개인 도메인이며 인증서를 셀프 싸인 인증서를 만들어서 TLS 서버 설정을 했다고 치자. 이렇게 되면 Docker 가 TLS 를 이용해서 서버에 접속을 하기 위해서는 서버 TLS 인증서에 대응하는 클라이언트 인증서가 필요하게된다.
Docker 클라이언트 인증서
Docker 는 클라이언트 인증서를 /etc/docker/certs.d 디렉토리에서 찾게 된다. 이 디렉토리에 Docker 레지스트리 서버 호스트 이름과 같은 이름으로 디렉토리를 생성하고 그 안에 클라이언트 인증서를 세팅하면 된다.
stack 이 생성 정상적으로 생성되면 3개의 Service가 생성된다. 각각 Traefik, Portainer, Registry 이며 Portainer 는 Traefik 에 URI Reverse 로 연결된다. Portainer 의 URL 은 http://<IP>/portainer 이다.
Docker 를 단순한 빠르고 가볍게 애플리케이션을 구성하게 해주는 컨테이너(Container) 로만 이해하는 건 좁은 시각이다. Docker 는 이러한 컨테이너들을 위한 네트워크 환경도 제공하고 있다. 이것은 Docker 가 종합적인 IT 인프라를 제공할 수 있다는 것을 의미한다.
Docker 네크워크 상태
Docker 를 호스트에 설치하면 우선적으로 다음과 같은 네트워크 장치가 하나 보인다.
ZSH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~$ifconfig
docker0 Link encap:Ethernet HWaddr02:42:33:37:4b:7f
컨테이너를 하나도 실행하지 않은 상태에서 단지 Docker 데몬이 실행만 되어도 이렇게 docker0 라는 네트워크 장치가 나타난다. 아이피는 172.17.0.1 이며 브로드캐스트(Broadcast) 가 172.17.255.255 인 것으로 봐서 docker0 장치가 속하는 네트워크 대역은 172.17.0.0/16 임을 알 수 있다.
docker0 는 Docker 가 실행되면서 자동으로 생성되는 가상의 네트워크 장치다. 네트워크 장치에도 여러가지 역할 혹은 모드를 부여할 수 있는데, docker0 가상네트워크는 브릿지모드로 동작하도록되어 있다.
ZSH
1
2
3
~$brctl show
bridge name bridge id STP enabled interfaces
docker08000.024233374b7fno
docker0 가상네트워크가 브릿지 모드로 동작한다는 것은 이 브릿지에 연결한 네트워크 장비들을 외부와 통신하도록 해준다. 말 그대로 다리 역할을 하는 것이 docker0 이다.
우분투 컨테이너의 Default Gateway 는 컨테이너를 실행한 호스트의 Docker 가상네트워크인 docker0 를 가르키고 있다. 결국 Docker 컨테이너의 모든 네트워크는 호스트의 docker0 를 가르키고 이는 모든 외부와의 트래픽은 docker0 을 통해서만 가능하다.
이를 도식으로 나타내면 다음과 같다.
Docker0 브릿지 네트워크
이 docker0 브릿지 네트워크는 Docker 네트워크중에 하나다.
ZSH
1
2
3
4
5
~$docker network ls
NETWORK ID NAME DRIVER SCOPE
13ed3c333711bridge bridge local
84d5591305cahost host local
352cc4d7e9bfnone nulllocal
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 디렉토리에 생성된다. 이에 대한 정보는 컨테이너 상세정보를 보면 알 수 있다.
Network Namespace 를 통해서 컨테이너에 네트워크 관련 명령어를 내릴 수 있다.
docker0 와 eth0 의 연결: iptables -t nat
docker0 에서 외부 네트워크 연결을 위해서 특별한 프로그램을 별도 운영하진 않는다. 단지, NAT 를 위한 Iptables 를 세팅한다. 이것은 오래전에 리눅스 박스를 라우터 장비로 이용하기 위한 방법인(지금에 비교를 하자면 리눅스 박스를 공유기로 이용하는 방법이다.) 마스커레이드(MASQUERADE) 과 완전 동일하다.
docker0 가상네트워크를 공유기로서 동작하도록 설정하는 것이다. iptables 를 한번 봐보자.
Docker 를 서비스 모드로 실행을 시키면 나중에 문제가되는게 하나 있다. 예를들어, MySQL 을 Docker 를 이용해서 서비스 모드로 실행시켰다고 치자. 그러면 MySQL를 위한 데이터 디렉토리 Container 내에 생성된다. 만일 MySQL을 패치된 버전으로 올리고 싶다면 어떻게 될까? 이럴 경우에 데이터 디렉토리를 Container 내에서 백업할 방법이 없다.
이를 위해서 Docker 에는 Data Volumes 을 추가 할 수 있다.
Data Volumes
데이터 볼륨은 호스트 파일 시스템을 컨테이너에 마운트 하는 방법이다. 따라서 Container 가 중지되었거나 Container 를 삭제했다 하더라도 호스트 파일 시스템에는 Container 에서 사용했던 데이터 볼륨의 데이터가 살아 있게 된다.
이를 이용하면 MySQL, Redis 와 같이 데이터를 보존해야 하는 서비스를 위해서 호스트에 파일을 저장하도록 할 수있다.
Docker Container 를 생성한다. -t 는 tty 를 할당하고 하게 -i 는 Interactive 를 말한다. 보통 -t, -i 옵션을 붙여서 -it 로 붙여서 쓴다. -i 옵션으로 인해서 STDIN 을 받을 수 있는데 이는 /bin/bash 를 통해서 받도록 한다.
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 Registry 라고 있는데, Docker 내부 컴포넌트중에 하나 인데 Docker Hub 에서 다운받아 가지고 있는 저장소라고 보면된다. 저장소이다보니까 공개된 Docker Hub 대신에 Private Hub 를 구축할때에 Docker Registry 를 사용기도 한다.
말을 잘해야 한다
여기서 컨테이너에서 동작하는 Image 라는 말은 잘못된 말이다. 거꾸로다. Image 를 가지고 컨테이너를 생성한다고 해야 맞는 말이다. 컨테이너는 가상환경이 아니다. 소프트웨어 격리 공간인데, 이 공간은 소프트웨어가 있어야만 가능하다. 따라서 Image 를 가지고 컨테이너를 생성한다고 해야 맞다.
docker search 이미지
이미지를 찾고 싶을때 사용한다. mariadb 로 검색을 하면 여러개가 나오는데 공식적으로 배포하는 이미지 말고는 전부다 커스텀 이미지라고 보면 된다.
docker pull 이미지:태그
이 명령어는 Docker Hub 에서 이미지를 다운받을 때 사용한다. 이미지를 Docker 서버에 다운로드 한다. 그런데, 이미지라도 버전이 있을 수 있다. 예를들어 mariadb 의 경우에도 다양한 버전이 존재한다. 이때 태그에 가능한 버전을 기입하면 그 버전을 다운받게 된다. 항상 최신의 버전을 다운받고 싶다면 latest 로 해주면 된다.
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이 나타난다.