도커 - Dockerfile, docker-compose!

Dockerfile

nginx서버 /usr/share/nginx/html/index.html 디렉토리에 index.html파일을 삽입하여 다시 이미지를 만들고 싶을때
이미 존재하는 nginx 서비스를 사용할때 다시 이미지화 할 수 있으면 좋지 않을까?

이미지 레이어를 사용해 효과적으로 이미지를 생성할 수있도록 스크립트를 제공하는데 Dockerfile이다.

다음과 같이 Dockerfile을 작성

FROM nginx:latest
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

index.html파일에 <h1>Hello Docker!<h1> 삽입!

docker build -t mywebserver ./
현재 디렉토리의 Dockfile을 사용해서 mywebserver란 이미지를 새성한다.

docker build -t 생성이미지이름:태그 Dockerfile위치

출력값

Sending build context to Docker daemon  3.072kB
Step 1/4 : FROM nginx:latest
 ---> 540a289bab6c
Step 2/4 : COPY index.html /usr/share/nginx/html/index.html
 ---> Using cache
 ---> 560cc55dd2ad
Step 3/4 : EXPOSE 80
 ---> Using cache
 ---> 6c70c38cad8f
Step 4/4 : CMD ["nginx", "-g", "daemon off;"]
 ---> Using cache
 ---> fc4c50b45ccd
Successfully built fc4c50b45ccd
Successfully tagged mywebserver:latest

mywebserver라는 이미지가 생성되었다, 기존의 nginx이미지를 사용해서!

주의: 도커파일의 이름 기본값은 Dockerfile이며 이를 변경하면 별도의 이름 지정 속성을 사용해야 한다.

Dokcerfile 명령어

도커파일은 docker이미지를 생성하기 위한 파일로 기존 이미지에 여러가지 설정을 붙여 새로운 이미지를 만들때 사용하는 파일이다.

명령어 설명
FROM 가장 기본이 되는 이미지 지정, 필수 입력 항목
RUN 커맨드 실행, 실행할 때 마다 새로운 레이어가 생성된다, 때문에 왠만한건 한줄처리로 해결하는 것이 좋다. RUN apt-get update && apt-get install -y curl nginx
CMD docker run 명령인자 생략시에 실행되는 default 명령어 혹은 default 파라미터, 파라미터는 ENTRYPOINT 에서 사용된다. 한번만 입력 가능.
ENTRYPOINT 데몬 실행, docker run 시 실행되는 명령어, CMD 와 다르게 생략되지 않는다
ONBUILD 빌드완료후 실행할 명령, 여기서 명령은 Dockerfile의 명령(ADD, COPY 등), ONBUILD를 통해 새로운 이미지 레이어가 생성되고 지정된 명령어를 수행한다.
STOPSIGNAL 컨테이너 종료시 송신하는 시그널 설정. 시그널번호(9), 시그널명(SIGKILL) 지정 가능
HEALTHCHECK 컨테이너 안의 프로세스가 정상동작하는지 체크, ex: HEALTHCHECK --interval=5m --timeout=3s CMD cur -f http://localhost/ || exit 1
ENV 환경변수 지정, 공백이 표함될 경우 쌍따옴표 필수, 명령 사용마다 레이어가 늘어남으로 \ 로 계행하여 한줄로 사용하는 것이 효율적. docker run --env 로 기본값 대체 가능
ARG Dockerfile 안의 환경변수, ENV 와는 달리 Dokcerfile 안에서만 사용 가능. docker run --build-arg 로 기본값 대체 가능
WORKDIR 작업 디렉터리 지정, 여러번 사용해 디렉토리 지정이 가능하며 상대 경로 입력해 현 경로에서 이동함.
LABEL 라벨 지정, key value 형식의 각종 코멘트(작성자, 버전 등) 지정
EXPOSE 공개 포트번호 지정, ex: EXPOSE 8080
ADD 파일 및 디렉토리 추가, ADD <호스트 파일경로> <Docker이미지 파일경로>, 호스트 파일 경로는 HTTP URL 가능. *, ? 기호 사용하여 파일 이름 형식 지정 가능.
WORKDIR 과 상대경로로 디렉터리 지정, 호스트 파일경로를 URL 로 사용시 마지막 디텍토리 슬래시 / 를 제거해 파일명 지정 가능, 제거하지 않을경우 URL로 파일명 지정.
tar, gzip 등의 압축은 자동으로 해제됨, 단 URL의 경우 풀리지 않음.
.dockerignore 을 통해 특정 파일 제외 가능
COPY 파일 및 디렉토리 추가, ADD 가 더 많은 기능을 포함하고 있다. 단순 로컬 파일을 이미지로 전달하고 싶을땐는 COPY 를 사용.
USER RUN, CMD, ENTRYPOINT 실행 사용자 지정. ex: USER [username/UID]
SHELL 기본 쉘 설정. ex: SHELL ["/bin/sh", "-c"]
VOLUME 볼륨 마운트 ex: VOLUME ["/data/log", "/var/log"], 인자를 하나만 사용할 경우 호스트와 컨테이너 모두 해당 디렉토리를 사용.
MAINTAINER 작성자 지정

RUN, CMD, ENTRYPOINT 차이점: https://blog.leocat.kr/notes/2017/01/08/docker-run-vs-cmd-vs-entrypoint

RUN - 새로운 레이어에서 명령어를 실행하고, 새로운 이미지를 생성한다. 보통 패키지 설치 등에 사용된다. e.g. apt-get

CMD - default 명령/파라미터를 설정한다. docker run 실행 시 실행 커맨드를 주지 않으면 이 default 명령이 실행된다. 실행 커맨드 입력 CMD 설정은 생략된다.

ENTRYPOINT - CMD 와 차이점은 실행 커맨드 입력시에도 생략되지 않는것. 아래와 같은 설정이 가능하다.

FROM ubuntu:16.04
ENTRYPOINT ["top"]
CMD ["-d", "10"]

해당 Dockerfilesample 이란 이미지를 생성 후 아래처럼 사용 가능

docker run -it sample ## CMD 의 인자를 그대로 사용, 10초 간격 갱신
docker run -it sample -d 2 ## CMD 의 인자 생략, 2초 간격 갱신    

FROM만 필수항목이고 나머지는 모두 없어도 된다.
(나머지 속성은 모두 기본이미지에 살을 붙이는 속성)

FROM 명령만 적어 Dockerfile을 생성해보자.
ubuntu 컨테이너에 nginx를 설치하고 index.html파일을 삽인한 후 다시 이미지화.

## Dockerfile.base
## Dockerfile 에선 주석처리를 ## 을 이용
## ubuntu 컨테이너 생성
FROM ubuntu:16.04

## 명령어 실행
RUN apt-get update && apt-get install -y -q nginx

## 현재 디렉토리에 index.html을 해당 경로에 복사
COPY index.html /usr/share/nginx/html

## 데몬 실행 daemon off는 포그라운드로 실행 
CMD ["nginx", "-g", "daemon off;"]

docker images로 내가 지정한 이미지가 생성됬는지 확인하고 docker history 이미지명:태그명으로 Dockerfile을 실행하면서 어떤 명령들이 실행되었는지 확인가능하다.

$ docker build -t ubuntunginx:1.0 -f Dockerfile.base .
$ docker history ubuntunginx:1.0
IMAGE               CREATED              CREATED BY                                      SIZE                COMMENT
f4dcde6a2f2e        About a minute ago   /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B
f4b126ec4a47        About a minute ago   /bin/sh -c #(nop) COPY file:b673a17565a2aafc…   23B
4b48682856d7        2 minutes ago        /bin/sh -c apt-get update && apt-get install…   82.1MB
5f2bf26e3524        8 days ago           /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           8 days ago           /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           8 days ago           /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           8 days ago           /bin/sh -c rm -rf /var/lib/apt/lists/*          0B
<missing>           8 days ago           /bin/sh -c #(nop) ADD file:9511990749b593a6f…   123MB

-f : Dockerfile 별도 이름 지정

생각해보면 docker hub에서 이미지 다운받아 컨테이너를 만들때 부터 어떤 포트가 들어갈지, 어떤 명령을 수행할지 이미 설정된 이미지들이 있는데 모두 Dockerfile로 만들어진 이미지들이다.

union file system

위의 출력값을 통해 도커는 union file system을 사용함을 알 수 있다.
계층화된 파일 시스템을 사용해 중복 데이터를 피해 효율적인 이미지 관리를 할 수 있다.
(시간 단축, 데이터 효율!)

docker images로 확인해 보면 기존에 생성했던 ningx 이미지를 사용해 새로만든 mywebserver의 이미지를 생성한다.

REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
mywebserver               latest              fc4c50b45ccd        26 hours ago        126MB
nginx                     latest              540a289bab6c        4 days ago          126MB
docker run --name newNginx -d -p 80:80 mywebserver

위 명령어로 해당 이미지를 실행시키고 위에서 정의한 index.html 파일이 /usr/share/nginx/html 디렉터리에 들어있는지 확인

docker image inspectnginx:latest 이미지와 mywebserver 이미지를 확인하면

// nginx:latest
"GraphDriver": {
    "Data": {
        "LowerDir": "/var/lib/docker/overlay2/3365ae3267b423c3807e23070b9d68c6aeb907291c4e1b3a7029d9e998fa23cd/diff:/var/lib/docker/overlay2/dc2502d1901912453f88751bf1e058db15e4c6f7ef1517454c2787c4f15bcc41/diff",
        "MergedDir": "/var/lib/docker/overlay2/b3670092ad204d3be3e63222eabadd9e16c81d7f8077c200fc9ff142a6ea4754/merged",
        "UpperDir": "/var/lib/docker/overlay2/b3670092ad204d3be3e63222eabadd9e16c81d7f8077c200fc9ff142a6ea4754/diff",
        "WorkDir": "/var/lib/docker/overlay2/b3670092ad204d3be3e63222eabadd9e16c81d7f8077c200fc9ff142a6ea4754/work"
    },
    "Name": "overlay2"
},
// mywebserver
"GraphDriver": {
    "Data": {
        "LowerDir": "/var/lib/docker/overlay2/b3670092ad204d3be3e63222eabadd9e16c81d7f8077c200fc9ff142a6ea4754/diff:/var/lib/docker/overlay2/3365ae3267b423c3807e23070b9d68c6aeb907291c4e1b3a7029d9e998fa23cd/diff:/var/lib/docker/overlay2/dc2502d1901912453f88751bf1e058db15e4c6f7ef1517454c2787c4f15bcc41/diff",
        "MergedDir": "/var/lib/docker/overlay2/bff24d6762626ed3eeeb8c7405c450f75fa53920b9bac02422fc84e09c960e16/merged",
        "UpperDir": "/var/lib/docker/overlay2/bff24d6762626ed3eeeb8c7405c450f75fa53920b9bac02422fc84e09c960e16/diff",
        "WorkDir": "/var/lib/docker/overlay2/bff24d6762626ed3eeeb8c7405c450f75fa53920b9bac02422fc84e09c960e16/work"
    },
    "Name": "overlay2"
},

MergedDir, UpperDir, WorkDir은 도커 이미지 구축을 위한 데이터로 두 이미지의 내용이 일치하는 부분이 상당히 많다.

arm docker build

apple silicon 에서 docker build 시 각별한 주의가 필요하다.

apple silicon 에서 빌드한 이미지는 arm 시스템에서 동작하는 Docker 이미지가 생성되고
일반 amd CPU 를 사용하는 시스템에서 해당 이미지를 실행하면 아래와 같은 오류가 발생한다.

exec format error

빌드시 아래와 같이 amd 아키텍처에서 실행할 것임을 명시해야한다.

docker build --platform linux/amd64 -t docker-test .

not certified ssl 등록

공식 Docker Registry 가 아닌 사설 SSL 인증서가 적용된 Private Registry 지정시 아래와 같은 오류가 발생할 수 있다.
https 를 사용하더라도 공식 SSL 인증서가 아니라면 아래 에러가 docker login, pull, push 할 때 마다 발생한다.

"SSL certificate problem: self signed certificate in certificate chain"

아래 파일에서 insecure-registries 속성을 추가해서 해결 가능하다.

  • ubuntu: /etc/docker/daemon.json
  • mac: /Users/{username}/.docker/daemon.json
{
  "builder": {
    "gc": {
      "defaultKeepStorage": "20GB",
      "enabled": true
    }
  },
  "experimental": false,
  "insecure-registries": [
    "https://core.harbor.domain"
  ]
}

멀티스테이지 빌드(Multi-Stage Build)

Spring Boot 서버를 실행하는 Docker 이미지를 만드려면 jar 파일을 COPY 하여 실행시키는 Dockerfile 을 작성한다.
jar 파일을 만들기 위해 host 에는 JDK 가 설치되어 있어야 하는데, jar 를 만드는 과정까지 Docker 컨테이너에서 수행할 수 있다.

# 빌드 단계
FROM gradle:8.8-jdk17 AS build
WORKDIR /app

# 프로젝트 소스 복사
COPY . .

# Gradle 빌드 실행
RUN gradle build --no-daemon

# ------------------------------

# 실행 단계
FROM openjdk:17-jdk-slim
WORKDIR /app

# 빌드된 JAR 파일 복사
COPY --from=build /app/build/libs/*.jar app.jar

# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "app.jar"]

docker-compose

지금까지 도커와의 링크를 위해 네트워크, 볼륨, 혹은 --link 속성으로 컨테이너간의 연결을 진행하였는데
docker-compose 혹은 dockerfile을 사용하면 복잡한 명령어를 파일단위로 관리할 수 있다.

docker-compose는 일종의 툴로 docker-compose.yaml 파일을 사용해 여러개의 컨테이너를 한번에 생성할 수 있다.

Dockerfile 로 이미지를 생성하고
docker-compose 로 생성한 이미지를 컨테이너화 시킨다.

컨테이너화 할때 사양한 설정 명령을 삭성해야 하며 docker run 명령이 10줄이 넘어갈 수 있다.
docker-compose는 단순 명령 실행의 떨어지는 가독성을 보완하고 1개의 컨테이너만 생성하는 것이 아니라 여러개의 컨테이너를 연관지어 한꺼번에 생성 가능하게해준다.

docker compose 설치 및 운영

apt-get install로 설치가능하지만 버전에 따른 오류가 많기에 아래 명령으로 실행

https://github.com/docker/compose/releases?after=1.23.1

위 사이트에서 docker가 제공해준 실행파일을 다운 받은 후 권한을 설정한다.

$ sudo curl -L https://github.com/docker/compose/releases/download/1.21.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose
$ sudo chown kouzie /usr/local/bin/docker-compose

그후 vidocker-compose.yml파일 생성

샘플로 wordpress와 wordpress가 사용할 mysqlDB를 생성해보자.

version: '2'
services:
    db:
        image: mysql:5.7
        volumes:
            - ./mysql:/var/lib/mysql
        restart: always
        environment:
            MYSQL_ROOT_PASSWORD: wordpress
            MYSQL_DATABASE: wordpress
            MYSQL_USER: wordpress
            MYSLQ_PASSWORD: wordpress
    wordpress:
        image: wordpress:latest
        volumes:
            - ./wp:/var/www/html
        ports:
            - "8080:80"
        restart: always
        environment:
            WORDPRESS_DB_HOST: db:3306
            WORDPRESS_DB_PASSWORD: wordpress

docker-compose up명령으로 docker-compose.yml파일 실행

$ docker-compose up
Starting kouzie_wordpress_1 ... done
Starting kouzie_db_1        ... done

실행과 동시에 출력되는 로그가 계속 뜨는데 도중에 나갈 수 없다.
시작할때 백그라운드로 실행해야 한다.

$ docker-compose up -d : 백그라운드로 실행

묶음으로 실행되기 때문에 해당 컨테이너들끼리 사용하는 브릿지 네트워크가 자동으로 생기는데 파일이 존재하는 디렉터리 명으로 생성된다.
kouzie 홈디렉토리에서 docker-compose.yml을 생성해 실행했기 때문에 kouzie_default 라는 브릿지 네트워크가 생겼다.

그리고 컨테이너 명도 별도로 지정하지않아 db와 wordpress앞에 디렉터리명이 붙어 생성된다.

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
2e7c11994ccc        wordpress:latest    "docker-entrypoint.s…"   10 minutes ago      Up 23 seconds       0.0.0.0:8080->80/tcp   kouzie_wordpress_1
f4fe464220e4        mysql:5.7           "docker-entrypoint.s…"   10 minutes ago      Up 23 seconds       3306/tcp, 33060/tcp    kouzie_db_1

https://github.com/asashiho/dockertext2/tree/master/chap07

이번엔 redis와 파이썬 웹서버를 묶어사용하는 docker-compose.yml을 생성

version: '3.3'
services:
  ## WebServer config
  webserver:
    build: .
    ports:
     - "80:80"
    depends_on:
     - redis

  ## Redis config
  redis:
    image: redis:4.0

webserver의 경우 build: . 이 적혀있는데 이는 같은 디렉토리에 있는 Dockerfile을 빌드해서 생성한 컨테이너를 사용하겠다는 뜻.

## Base Image
FROM python:3.6

## Maintainer
LABEL maintainer "Shiho ASA"

## Upgrade pip
RUN pip install --upgrade pip

## Install Path
ENV APP_PATH /opt/imageview

## Install Python modules needed by the Python app
COPY requirements.txt $APP_PATH/
RUN pip install --no-cache-dir -r $APP_PATH/requirements.txt

## Copy files required for the app to run
COPY app.py $APP_PATH/
COPY templates/ $APP_PATH/templates/
COPY static/ $APP_PATH/static/

## Port number the container should expose
EXPOSE 80

## Run the application
CMD ["python", "/opt/imageview/app.py"]

Dockerfile에는 각종 파일을 복사하고 app.py코드르 실행하는 스크립트가 작성되어있다.

docker-compose up 실행, 실행하는 주소로 접속해보면 그림처럼 출력된다.

docker13

docker-compose 상태 확인

docker-compose ps 명령으로 컨테이너 동작 상황을 알 수 있다.
docker-compose.yml파일이 있는 위치에서 실행해야한다.

$ docker-compose ps
       Name                     Command               State         Ports
--------------------------------------------------------------------------------
chap07_redis_1       docker-entrypoint.sh redis ...   Up      6379/tcp
chap07_webserver_1   python /opt/imageview/app.py     Up      0.0.0.0:80->80/tcp

그 외에도 각종 명령어 들이 존재

docker-compose port webserver 80 (80번포트가 어떤 외부포트와 매핑되어있는지 출력) docker-compose config (docker-compose.yml파일 안의 정보 출력) docker-compose kill -s SIGINT (kill -s 9 하면 서버를 종료시킨다) docker-compose rm (컨테이너 삭제) docker-compose down (kill -s 9 and rm)

down후에 ps로 한번 모든 컨테이너가 종료되었는지 확인해보자.

docker-compose 앵커

yaml 설정 중복을 줄이고 설정을 재사용하기 위해 사용

# 앵커이름 및 내부에 사용할 yaml 설정 지정
x-test-logging: &default_logging
  driver: "json-file"
  options:
    max-size: "10m" # docker 로그 최대 사이즈
    max-file: "3"   # docker 로그 최대 개수

services:
  demo-log-app:
    image: alpine
    command: sh -c "trap 'exit' SIGTERM; while true; do echo 'demo log'; sleep 2; done"
    logging: *default_logging

병합 키(<<) 문법을 사용하여 속성명부터 재활용할 수 있다.

x-test-logging: &default_logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"


x-common-settings: &common
  image: alpine
  logging: *default_logging

services:
  demo-log-app1:
    <<: *common
    command: sh -c "trap 'exit' SIGTERM; while true; do echo 'demo log1'; sleep 2; done"
  demo-log-app2:
    <<: *common
    command: sh -c "trap 'exit' SIGTERM; while true; do echo 'demo log2'; sleep 5; done"

docker-compose 환경변수

docker 컨테이너 생성시 환경변수를 -e 옵션으로 지정할 수 도 있지만 --env-file 을 사용해 설정할 수 도 있다.

docker run --env-file .env <이미지 이름>

docker-compose 에서는 .env 숨김파일을 docker-compose.yml 과 같은 위치에 지정해놓을 경우 docker-compose up 명령실행시 자동으로 환경변수 파일이 지정된다.

아래와 같이 .env 파일을 생성하고

GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=password

environment 속성에 환경변수 사용하도록 이름 지정

services:
  grafana:
    image: grafana/grafana:11.4.0
    user: "0:0"
    environment:
      - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER}
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
    ports:
      - "3000:3000"
    volumes:
      - ./volume/grafana:/var/lib/grafana

환경별로 다르게 설정하고 싶다면 .env 를 삭제하고 아래처럼 명시하여 실행 가능.

docker-compose --env-file dev.env up -d
docker-compose --env-file prd.env up -d

혹은 아래와 같이 environment 에 들어가 환경변수를 직접 env 파일에 명시할 수 있다.

# dev.env
GF_SECURITY_ADMIN_USER=dev
GF_SECURITY_ADMIN_PASSWORD=password
grafana:
  image: grafana/grafana:11.4.0
  env_file:
    - dev.env
  # environment:
  #   - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER}
  #   - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
  ports:
    - "3000:3000"
  volumes:
    - ./volume/grafana:/var/lib/grafana  # 데이터를 저장할 볼륨 마운트
  user: "0:0"  # root 권한으로 실행

카테고리:

업데이트: