왜 Docker를 사용했는가?
서로 다른 개발 환경
최근 주요 지표를 태블로(Tableau)로 시각화하는 작업을 준비하고 있다. 그 과정에서 데이터를 정제할 필요성을 느꼈고 파이썬(Python)을 사용하기로 결정했다. 개발에 들어가기에 앞 서 나와 파트장님의 파이썬 버전이 서로 다른 것을 확인했다. (파트장님 3.8 / 나3.10 / 최신 버전 3.11)
외부 모듈 설치의 불편함
사내 보안 설정에 의해 서버에서 직접적으로 외부 모듈을 설치 할 수 없는 환경을 제공받고 있다. 서버에서 직접 모듈을 설치하려면 그 때마다 요청을 해야하는 불편함이 있다.
그래서 Docker를 도입해 동일한 개발 환경 제공과 모듈관리에 있다. 또한 자주 있는 상황은 아니지만 코드를 배포와 함께 모듈을 설치하는 상황을 방지하고 싶었다.
어떻게 Docker를 적용했는가?
Dockerfile을 만들고 컨테이너 띄워보기
Dockerfile 만들기
먼저 Dockerfile을 간단히 작성했다. Django를 사용할 것이기에 모듈 설치와 프로젝트 생성을 위한 실행 코드도 추가했다.
# 이미지와 버전 명시
FROM python:3.11
# 프로젝트 경로 설정
WORKDIR /app
# 장고 모듈 설치
RUN pip install django
# 패키지 관리 파일 생성
RUN pip freeze > requirements.txt
# 장고 프로젝트 생성
RUN django-admin startproject config .
# 포트 설정
EXPOSE 8000
# 서버 실행
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
다음으로 이미지를 빌드해 볼 차례이다.
이미지 빌드하기
django-server 라는 이미지명으로 빌드 명령을 실행했고 Dockerfile에 작성한 명령이 잘 실행된 것을 확인 할 수 있었다.
$ docker build -t django-server .
[+] Building 1.7s (9/9) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 419B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/python:3.11 0.8s
=> [1/5] FROM docker.io/library/python:3.11@sha256:5329e75033c4446dc92d702cf8ebbeb63e549d9b83076a776f6753e10817fc3c 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> CACHED [3/5] RUN pip install django 0.0s
=> [4/5] RUN pip freeze > requirements.txt 0.3s
=> [5/5] RUN django-admin startproject config . 0.4s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:cb5aae78936a6e4e06ae4b53f000d62269fee8ab2fc80d721f3ef50af4e71a03 0.0s
=> => naming to docker.io/library/django-server 0.0s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
생성된 이미지는 이렇게 확인 할 수 있다.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
django-server latest cb5aae78936a 54 seconds ago 920M
컨테이너 실행하기
백그라운드로 실행하기 위해 -d 옵션을 추가하고 -p 포트는 8000을 사용했다.
$ docker run -d -p 8000:8000 django-server
82fba4580143514248835dbb0d38ac725e2856d5e089bf6c63685774506dba8a
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
82fba4580143 django-server "python manage.py ru…" 11 seconds ago Up 11 seconds 0.0.0.0:8000->8000/tcp eager_snyder
http://localhost:8000 으로 접속해 django 첫 화면이 정상적으로 출력되는 것을 확인했다.
로컬에서 작업할 수 있도록 소스 파일들을 복사했다.
$ docker cp eager_snyder:/app/manage.py .
$ docker cp eager_snyder:/app/config .
$ docker cp eager_snyder:/app/requirements.txt .
Dockerfile은 이렇게 다시 수정했다.
# 이미지와 버전 명시
FROM python:3.11
# 프로젝트 경로 설정
WORKDIR /app
# 패키지 관리 파일 복사
COPY requirements.txt .
# 패키지 설치
RUN pip install -r requirements.txt
# Container Port
EXPOSE 8000
# 서버 실행
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
이미지를 빌드하고 컨테이너로 웹 서버를 실행해보니 다음으로는 어떻게 이미지를 관리하고 작업자들 간에 공유할 수 있을지에 대한 고민을 시작했다.
Private Registry 구축하기
Docker Registry 테스트
도커 이미지를 공유하기 위해서는 registry 구축이 필요하다는 것을 알게 됐다. Docker Hub라는 선택지가 있지만 private하기 위해 docker registry를 내려받았다.
$ docker pull registry
5000번 포트를 사용해 docker-registry 컨테이너를 실행했고,
$ docker run --name docker-registry -d -p 5000:5000 registry
{registry ip}:{registry port}/{이미지(:tag)} 이 형식으로 태그를 지정해주면 registry에 올릴 수 있는 상태가 된다.
$ docker tag django-server localhost:5000/django-server
이제 이미지를 push 해본다.
$ docker push localhost:5000/django-server
Using default tag: latest
The push refers to repository [localhost:5000/django-server]
7d43c1e9464f: Pushed
72285758bcfa: Pushed
01b182a46d9f: Pushing [=====================================> ] 36.48MB/48.65MB
15ae05014fda: Pushed
1b38857d7bd5: Pushed
c63832a549a2: Pushed
67968e3386b6: Pushed
807e5e673844: Pushed
cfd0811d364e: Pushing [=====> ] 60.59MB/528.8MB
b86f260e173a: Pushing [====================> ] 63.3MB/152MB
6a1ebb98b0dc: Pushed
24b48387f467: Pushed
ae56c0c5405b: Pushing [======> ] 15.68MB/124.1MB
push가 완료된 다음 레지스트리를 호출해보면 이처럼 repositories에 django-server가 들어간 것을 확인할 수 있다.
$ curl -X GET http://localhost:5000/v2/_catalog
{"repositories":["django-server"]}
간편하게 registry를 구축해서 운영해볼까 했는데 조금 불편함이 느껴졌다.
Nexus 서버 구축
팀원 중 Nexus를 추천해준 이가 있었다. Nexus는 오픈 소스로 이미지 저장소 역할을 하면서 인증 기능이 있어 보안 측면에서 더 안전한 것으로 판단했고 그러는 와중 마침 서버에 docker 설치가 완료되어 nexus 구축을 시작했다.
먼저 서버에서 nexus의 데이터가 저장될 공간을 생성했다.
$ mkdir ~/nexus-data
다음으로 컨테이너를 띄우기 위해 nexus 접속 포트를 8081, 레지스트리 포트 5000으로 설정하고 앞서 만들었던 디렉토리를 볼륨으로 설정했다. 이미지는 sonatype/nexus3이다.
(sonatype은 소프트웨어 라이프사이클 관리(Software Lifecycle Management, SLM) 솔루션을 제공하는 미국의 기업이라고 한다.)
$ docker run -d -p 8081:8081 -p 5000:5000 --name nexus -v ~/nexus-data:/nexus-data sonatype/nexus3
컨테이너가 정상적으로 실행되고 {서버IP}:8081으로 접속하면 nexus 관리 페이지가 실행되고 계정을 생성한다. 여기서 생성된 계정은 로컬에서 docker login시 사용된다.
그럼 이미지를 올려보기 위해 로그인을 먼저 해보겠다. 앞서 생성한 아이디와 비밀번호를 입력하면 되고 그렇지 않고 이미지를 push하면 "no basic auth credentials" 문구를 확인 할 수 있다.
% docker login {Registry IP}:5000
Username: {아이디}
Password: {비밀번호}
Login Succeeded
이제 push를 실행했다.
Using default tag: latest
The push refers to repository [{Registry IP}:5000/django-server]
Get "https://{Registry IP}:5000/v2/": http: server gave HTTP response to HTTPS client
위와 같은 메세지가 뜨면서 push가 진행되지 않았다. 기본적으로 docker는 https를 사용해 registry에 연결한다. 당장에 인증서를 설치할 수 없는 상황이니 다른 방법을 찾아봤다.
docker 데몬 설정에 보면 insecure-registries가 있는데 여기에 {Registry IP}:5000를 설정해주면 된다. insecure-registries가 없으면 아래와 같이 입력해주면 된다. 입력한 후에는 docker 데몬을 재시작 해야한다.
{
"insecure-registries" : ["{Registry IP}:5000"]
}
처음에 이 설정을 서버에 해야한다고 착각하고 한참을 해메고 있었다. 인증서 기준으로 생각하고 있었는지 클라이언트에 해야한다는 것을 생각지 못했다. 그러다가 무의식적으로 반대로 설정해보자고 파트장님께 제안했고 그제서야 정상적으로 작동시킬 수 있었다.
Portainer 서버 구축
더이상 운영 리소스를 늘리지 않고 싶었지만 직접 서버에 들어가 이미지를 내려받고 컨테이너를 생성하는 것은 불편했다. 개발자란 불편함을 참지 못하는 법! docker 컨테이너 관리 및 배포를 위한 오픈 소스 웹 기반의 UI 도구인 Portainer를 발견했다. docker API를 기반으로하여, Docker 컨테이너, 이미지, 네트워크, 볼륨 등의 모든 구성 요소를 관리할 수 있다고 한다.
Nexus 때와 마찬가지로 데이터 경로를 생성하고 9000번 포트와 볼륨을 설정한 뒤 컨테이너를 실행했다.
$ mkdir ~/portainer-data
$ docker run --name=portainer -p 9000:9000 -d --restart=always -v ~/portainer-data:/data -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer
Portainer를 살펴보면서 다양한 것들을 지원하는 것을 확인할 수 있었다. 그 중에 Agent를 발견했고 이를 사용하면 여러 서버에 설치된 Portainer를 연결해 한 곳에서 컨테이너를 관리할 수 있도록 하는 도구였다. docker 서버마다 Portainer를 설치하면 어떻게 운영해야 하나 고민하는 와중에 참 반가운 일이었다. Agent 설치는 아래 결과 참고하면 된다. 특별히 설정이 어렵진 않으니 검색해보면 금새 찾아 설정해볼 수 있다.
결과
위에서 작업한 내용을 결과물만 다시 정리 해봤다.
docker daemon "insecure-registries" - 클라이언트에서 http 사용을 위한 설정
$ cat /etc/docker/daemon.json
{
"insecure-registries" : ["{Registry IP}:5000"]
}
Nexus Container
$ cd ~
$ mkdir /nexus-data
$ docker run --name=nexus -d -p 5000:5000 -p 8081:8081 -v /nexus-data:/nexus-data -u root sonatype/nexus3
Portainer Container
$ cd ~
$ mkdir /portainer-data
$ docker run --name=portainer -p 9000:9000 -d --restart=always -v /home/deploy/portainer-data:/data -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer
Portainer Agent Container
$ docker run --name=portainer-agent -p 9001:9001 -d --restart=always -v /home/deploy/portainer-data:/data -v /var/run/docker.sock:/var/run/docker.sock portainer/agent
Dockerfile
FROM python:3.11
# 프로젝트 경로 설정
WORKDIR /app
# 패키지 관리 파일 복사
COPY requirements.txt .
# 패키지 설치
RUN pip install -r requirements.txt
# Container Port
EXPOSE 8000
# 서버 실행
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
Docker-compose
version: '3'
services:
mysql8:
image: mysql:8.0
container_name: mysql8
restart: unless-stopped
tty: true
command: --authentication_policy=mysql_native_password --lower_case_table_names=1
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: manager
MYSQL_USER: manager
MYSQL_PASSWORD: manager
MYSQL_ROOT_PASSWORD: manager
SERVICE_TAGS: dev
SERVICE_NAME: mysql8
django:
image: {웹서버 IP}:5000/myapp
container_name: myapp
volumes:
- .:/app
ports:
- "8000:8000"
depends_on:
- mysql8
Registry 서버
- 5000: Docker Registry
- 8081: Nexus
- 9000: Portainer
개발 웹서버
- 8000: Python
- 9001: Portainer Agent
개발 DB서버
- 3306: MySQL
- 27017: MongoDB
[참고]
댓글