SOPT 34기 서버파트/Docker 스터디

[Docker] 이미지 빌드와 Dockerfile

softmoca__ 2024. 4. 28. 16:51
목차

- 이미지가 저장되는 두가지 원리 중 하나인 빌드
- 이미지 빌드를 위한 Dockerfile 명세서를 위한 문법

- 더욱 효율적인 이미지 빌드 방식 : 멀티 스테이징

 

이미지와 빌드(Build)

IaC(Infrastructure as Code): 인프라 상태를 코드로 관리

코드에 상세 작업 내용이 기재되며 사람이 아닌 프로그램이 수행을 해서 작업들을 빠르고 안전하게 실행할 수 있다.

또한 코드에 들어가는 내용은 프로그램이 작업하기 위한 일련의 작업 명세서로 사용된다.

그리고 이런 명세서를 github 같은 곳에 저장하면 인프라의 상태도 소스코드 처럼 버전 관리를 할 수 있다.

 

Docker에서는 Dockerfile이라는 소스코드를 사용해서 인프라의 상태를 저장하는 이미지를 만들수 있다.

 

도커 커밋 방식의 문제

커밋은 이미지를 만들 떄EO마다 컨테이너를 실행해야하고 사용자가 명령어를 직접 입력해야한다.

또한 커밋 하나당 하나의 이미지 레이어가 추가 되어 여러개의 레이어를 추가하고 싶을 때는 여러번의 커밋을 해야하며 사람이 직접 작업하면 문제가 발생할 수 있다.

 

 

도커 커밋 방식의 문제를 해결하는 Dockerfile 빌드 방식

빌드 방식은 임시 컨테이너를 생성하고 커밋하는것을 도커가 대신 수행해준다.

도커에게 어떤 작업을 수행할지를 코드로 작성한것이 dockerfile이다.

이미지 제작자가 도커가 이해할 수 있는 문법에 따라서 도커 파일을 작성하면 도커는 임시로 컨테이너를 실행하고 사용자가 정의한 작업을 수항한뒤 커밋을 실행하는 과정을 반복한다.

docker build -t 이미지명 dockerfile경로

 

이미지를 빌드하기 위해서는 문법에 맞게 도커 파일을 작성해야 하며 지시어와 지시어의 옵션으로 구성이 된다.

도커 파일 작성 실습

index.html 파일 수정후 dockerfile 작성

수정한 index.html파일을 nginx:1.23 이미지를 가져온 후 이미지에 있는 /usr/share/nginx/html/index.html로 덮어 쓰기

 

커밋 방식에서는 컨테이너 안에 직접 들어가서 이 index.html 파일을 덮어 쓰기 했지만 빌드 방식에서는 도커가 Copy 명령을 통해서 알아서 덮어 쓰기 해준다.

 

Copy명령어는 index.html 부분을 도커 파일과 같은 경로에서 찾은 다음 실행중인 컨테이너의  /usr/share/nginx/html/index.html로 대신 덮어쓰기 해주는 것이다.

 

 

docker build -t softmoca/buildnginx .

한킨 띄우고 .은 현재 폴더를 의미하고 이 현재 명령을 실행하는 01 01.buildnginx에 빌드에 사용하는 도커 파일이 있다는 것을 지정하는 부분이다.

 

docker run -d -p 80:80 --name build-nginx softmoca/buildnginx

빌드해서 생성된 컨테이너를 실행

html 설정을 추가로 해주지 않아 한국어가 깨졌지만 그래도 컨테이너가 잘 작동한다 !

 

 

 

커밋과 빌드 정리

커밋사용자가 직접 새로운 이미지를 만드는 방법이고 빌드도커 데몬이 Dockerfile에 적힌 지시어를 사용해서 이미지를 자동으로 만들어주는 방식이다.

 

도커 빌드는 IaC 개념으로 인프라의 상태를 도커 파일이라는 코드로 관리할 수 있다.

커밋에서 사용자가 직접 수행했던 일들을 Dockerfile의 지시어로 기록해놓고 필요할 때마다 도커 데몬에게 이미지 빌드를 맡기는 것이다.

 

하나의 커밋은 기존 레이어에 하나의 새로운 레이어를 추가한다.

그래서 여러 레이어를 쌓으려면 커밋을 완료한 이미지를 새로운 컨테이너로 만들고 다시 두 번째 레이어를 커밋하는 순서대로 여러 차례 커밋을 수행해야 한다.

하지만 Dockerfile을 사용하시면 도커가 직접 이 작업들을 반복해주기 때문에 여러 개의 레이어 구조를 편리하게 활용할 수 있다.

 

 

 

빌드 컨텍스트(Build Context)

- 이미지를 빌드할 때 사용 되는 폴더

 

이미지 빌드 방식 도커 데몬이 임시 컨테이너를 실행시키면서 레이어드를 하나씩 추가한다.

그래서 도커 데몬에게 도커 파일과 빌드에 사용되는 파일들을 전달해 주어야 하고 이렇게 도커 데몬에게 전달해 주는 폴더가 바로 빌드 컨텍스트이다. 이전 빌드 실습에서 인덱스와 도커 파일을 작성했었던 01.buildnginx 폴더가 이 빌드컨텍스트였다.

 

docker build 명령을 사용하면 빌드 컨텍스트가 도커 데몬에게 통째로 전달된다.

그래서 이 컨텍스트 안에 있는 dockerfile로 도커 데몬이 이미지를 빌드하는 것이다.

그리고 Dockerfile에서 COPY 지시어를 사용하면 빌드 컨텍스트에 있는 파일이 빌드에 사용되는 컨테이너로 복사되는 것이다.

그리고 도커 데몬은 빌드 컨텍스트에 있는 파일만 카피 명령으로 복사할 수 있다.

 

즉,  빌드컨텍스트는 도커 데몬이 이미지를 빌드할 때 전달되는 폴더이고 이 폴더 안에 도커 파일과 카피에 사용할 파일들이 모두 들어있어야 한다.

 

 

dockerignore 실습 

 

 

에러 문구를 확인해 보시면 large-junk.txt 파일을 찾을 수 없어서 에러가 발생한다.

 

 

도커파일(Dockerfile) 지시어 

 

app.js

app.get('/', (req, res) => {
  const color = process.env.COLOR || 'green';
  const username = '사용자';
  res.render('index', { color, username });
});


app.get('/:name', (req, res) => {
  const color = process.env.COLOR || 'green';
  const username = req.params.name;
  res.render('index', { color, username });
});

process.env는 컨테이너 OS에서 환경 변수를 불러 온다.

 

views/index.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple Node.js Web App</title>
    <style>
        body {
            color: <%= color %>;
        }
    </style>
</head>
<body>
    <h1>Env Color Application</h1>
    <p><%= username %>님, 환영합니다.</p>
    <p>현재 ENV 값으로 적용된 환경 변수는 <%= color %> 입니다.</p>
</body>
</html>

 

새로운 레이어를 추가하는 지시어추가하지 않는 지시어가 있다.

파일 시스템의 내용을 변경하는 부분이 있으면 일반적으로 레이어를 추가 하고  메타데이터에만 영향을 주는 부분은 레이어가 추가 되지 않는다. 그래서 이 지시어들을 잘 활용해서 이미지의 레이어의 개수를 관리할 수 있다.

 

docker build -f 도커 파일명 -t 이미지명 dockerfile경로

-f 옵션을 사용해서 실제로 빌드에 사용할 도커 파일의 이름을 지정해 줄 수 있다.

 

 

Dockerfile-basic

FROM node:14 

COPY ./ /

RUN npm install

CMD ["npm", "start"]
docker build -f Dockerfile-basic -t buildapp:basic .

 

 

 

 

 

WorkDIR 지시어는 다음 명령어들이 (컨테이너상의)작업할 디렉터리를 지정할 수 있다.

WorkDIR 다음에 나오는 지시어들은 이 WorkDIR에서 지정한 디렉터리를 기준으로명령어를 수행한다.

이 WorkDIR 지시어는 리눅스나 윈도우의 CD 명령과 비슷하다고 생각하면 된다.

 

USER 지시어는 명령어를 실행할 때 이 명령어를 사용할 기본 사용자를 지정한다.

도커 컨테이너가 실행될 때는 기본적으로 명령어들이 루트 사용자로 실행되기 때문에 실행된프로세스가 굉장히 많은 권한을 가질 수 있다.그리고 이 점은 보안에 취약할 수 있기 때문에 컨테이너가 필요 이상의 권한을 가지지 않도록 유저 지시어로 조절할 수 있다.

 

Expose 지시어는 컨테이너가 실행될 때 사용할 네트워크의 포트를 기재한다.

Expose를 사용하지 않아도 기본적으로는 컨테이너가 모든 포트를 사용할 수 있지만  도커 파일을 읽는 다른 사람에게 애플리케이션이 사용하는 포트를 문서처럼 기재하기 위해 사용한다.

보통은 애플리케이션 소스 코드 안에 애플리케이션이 사용할 포트를 지정하는데 이렇게 Docker 파일 안에도 Expose로 애플리케이션이 사용할 포트를 명시해 놓으시면굳이 소스 코드를 보지 않아도 Docker 파일만 가지고도 포트를 확인할 수 있다.

 

FROM node:14

WORKDIR /app

COPY . .

RUN npm install 

USER node

EXPOSE 3000

CMD ["npm", "start"]

 

 

 

COPY ./ /   에서 COPY . .으로 수정하는 이유는 

workdir에서 /app으로 지정해 놓으면 다음으로 나오는 모든 지시어들은 경로를 /app에서 실행하게 된다.

그래서 npm install 명령이나 npm start 명령을 모두 이 app이라는 경로안에서 실행하게 된다.

 

그래서 이 소스 코드를 복사할 때도 /app 폴더 안에 복사해야 한다.

그리고 COPY 명령어는 . 을 입력하시면 현재 경로로 지정이 되기 때문에 카피할 대상에 . 만 입력해도 /app으로 복사가 된다.

이렇게 workdir 지시어는 이후에 사용되는 지시어들에게 영향을 줄 수 있기 때문에가능한 from 다음에 바로 작성하시는 것이 좋다.

그리고 이 workdir로 특정 경로를 지정해 놓으면 기존의 Node.js 이미지에 있던 파일 시스템과 섞이지도 않고 별도의 폴더로 만들어서 관리할 수 있기 때문에 초반에 경로를 지정해 두는 것이 좋다.

 

 

 

1. 이미지 빌드

docker build -f Dockerfile-meta -t buildapp:meta .

2. 컨테이너 실행 및 테스트 (localhost:3000)

 

docker run -d -p 3000:3000 --name buildapp-meta buildapp:meta

 

app.get('/', (req, res) => {
  const color = process.env.COLOR || 'green';
  const username = '사용자';
  res.render('index', { color, username });
});

 

위와 같이 별도의 환경변수를 사용하지 않아 기본값인 초록색으로 나온다 !

 

 

 

 

 

arg로 적용된 값은 컨테이너로 실행할 때는 적용이 되지 않기 때문에 기본값인 초록색으로 적용된걸 확인 !

 

 

 

 

 

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

- 도커 파일에서 두개의 베이스 이미지를 활용하는 방법.

보통 애플리케이션을 빌드하는 과정에서 만들어지는 파일들이 용량을 많이 차지한다.

이 파일들을 실제로 애플리케이션이 실행되는 데는 사용되지 않기 떄문에 이미지를 빌드에 사용하는 이미지와 실제로 애플리케이션이 실행하는 이미지로 나누는 것이다.

 

 

 

다른 스테이지에서 파일을 가져올 때--from 옵션을 사용해서 스테이지의 AS 뒤에 있는 이름을 지정한다.

그래서 maven 빌드에 사용된 모든 파일들은 제외하고 오로지 빌드를 통해서 만들어진  결과물인 jar파일만 가져올 수 있다.

 

이렇게 멀티 스테이지를 사용하면 싱글 스테이지에 비해 작은 메모리를 사용한다는것을 확인 

 

단일 스테이지 빌드를 사용했을 때는 이 이미지 안에 복사해온 소스 코드와 애플리케이션을 빌드할 때 다운받았던 외부 라이브러리 파일들까지 모두 저장이 되어 있기 때문에 불필요한 파일들이 많이 쌓여 있는 상태이다.

빌드를 사용한 Maven 도구도 애플리케이션을 빌드할 때만 사용하지 애플리케이션을 실행할 때는 사용이 되지 않기 때문에 애플리케이션을 실행하는 베이스 이미지도 불필요하게 커진 상태이다.

 

그래서 이 단일 스테이지 빌드를 두 가지 단계로 쪼개서 첫 번째 이미지는 빌드에 필요한 메이븐만 포함되어 있는 메이븐 이미지를 사용하고 두 번째로 애플리케이션 실행에 사용하는 이미지는 자바 런타임만 포함되어 있는 OpenJDK 이미지를 사용했다.

 

그리고 첫 번째 Maven 이미지에서 소스 코드를 복사하고 이 소스 코드를 애플리케이션으로 빌드 했고 결과물로 만들어진 아티팩트인 app.jar 파일을 실행에 사용하는 OpenJDK 이미지에서 복사해온 것이다.

이렇게 복사를 해왔기 때문에 실제 결과물로 만들어지는 이미지에는 OpenJDK 이미지와 애플리케이션인 jar 파일만 남아있게 된다.

이렇게 멀티스테이징 기술을 잘 활용하시면 애플리케이션 이미지의 크기를 크게 줄일 수 있다.