Docker & Kubernetes: 실전 가이드 -2022년판
컨테이너를 생성하고 관리하기 위한 도구
버츄얼 머신을 사용한다고 가정하면
FROM node:14 # 사용할 이미지, Nodejs를 사용할 수 있다
WORKDIR /app # /app 디렉토리에서 작업한다
COPY package.json . # 패키지 파일을 현재 위치로 복사한다
RUN npm install # 종속성을 설치
COPY . . # 나머지 코트를 복사
EXPOSE 3000 # 3000 포트를 외부에 노출함
CMD [ "node", "app.mjs" ] # 실행한다
docker build .
컨테이너가 통신하려는 포트가 있으므로 (EXPOSE 3000) 포트를 열어줘야 한다
docker run -p 3000:3000 $IMAGE_ID
3000에 3000을 게시한다
컨테이너 대신 포트 3000에서 실행되는 애플리케이션과 연결할 수 있다
-> 컨테이너와 호스트 운영체제 사이에는 디폴트 연결이 없기 때문에
컨테이너에서 실행 중인 애플리케이션에 HTTP 요청을 보내려면 통신하려는 컨테이너의 포트를 열어야 한다.
docker ps
로 실행중인 컨테이너 이름을 가져온다
docker stop $CONTAINER_NAME
컨테이너가 중지되고 종료된다.
이미지는 모든 설정 명령과 모든 코드가 포함된 공유 가능한 패키지이다.
컨테이너는 그러한 이미지의 구체적인 실행 인스턴스이다.
이미지 하나로 여러 컨테이너를 생성할 수 있다.
- 공식적인 도커 이미지를 들고올 수 있다
- `docker run node`
- node를 기반으로 하는 컨테이너를 생성한다
docker ps -a
-a
: 도커가 생성한 모든 컨테이너, 모든 프로세스가 표시된다.
docker run -it node
-it
: 컨테이너 내부로 들어가서 상호작용 할 수 있다
도커파일이란?
자체 이미지를 빌드할 때 실행하려는 도커에 대한 명령이 포함된다.
FROM node:14 # 다른 베이스 이미지에 나의 이미지를 구축할 수 있다
WORKDIR /app # 이후 명령이 /app 하위에 실행 됨
COPY package.json . # 먼저 복사를 해서 npm install 다음에 소스코드 복사를 함
# 소스코드 복사를 하더라도 이전 레이어의 변경이 없다
RUN npm install # 종속성을 설치, RUN은 이미지가 빌드될 때 실행된다
COPY . ./ # 이미지 외부 경로, 파일을 저장해야 하는 이미지 내부의 경로
# 이 프로젝트의 모든 폴더, 하위 폴더 및 파일을 복사해야 한다고 도커에 알린다
EXPOSE 80 # 80 포트를 외부에 노출함
CMD [ "node", "app.mjs" ] # CMD는 이미지를 기반으로 컨테이너가 시작될 때 실행된다
도커 파일로 이미지를 빌드한다
docker build .
.
: 도커 파일이 있는 경로
도커 컨테이너를 실행한다
docker run $IMAGE_ID
도커 컨테이너를 멈춘다
docker stop $CONTAINER_NAME
컨테이너와 컨테이너 내부에서 실행 중인 노드 서버가 종료된다
EXPOSE 80
을 했지만 포트 연결이 안 되는 이유?
실제로 이 명령은 문서화 목적으로만 추가되었다.
컨테이너의 프로세스가 이 포트를 통해 노출할 것임을 적어놓았다.
docker run -p 3000:80 $IMAGE_ID
-p
옵션으로 도커에게 어떤 로컬 포트가 도커의 특정 포트에 엑세스 할 수 있는지 알려준다.
3000
: 로컬 포트 지정
80
: 내부 도커 포트 지정 (EXPOSE 80
)
이제 localhost:3000으로 접속이 가능하다.
docker build .
도커 이미지를 다시 빌드한다면 엄청 빠르게 빌드하는데, 이는 캐시를 사용했기 때문이다.
도커는 모든 명령 결과를 캐시하고 이미지를 빌드할 때 명령을 다시 실행할 필요가 없다면 캐시를 사용한다.
이를 레이어 기반 아키텍쳐라고 한다. 모든 명령은 Dockerfile의 레이어를 나타낸다
docker --help
도커 도움말
docker ps
모든 컨테이너를 리스트 할 수 있다
-a
: 더이상 실행되지 않는 중지된 컨테이너를 포함하여 과거에 있었던 모든 컨테이너를 표시
docker run
을 실행하면 이미지를 바탕으로 새 컨터이너를 만들어서 실행한다.
docker start $CONTAINER_NAME 으로 컨테이너를 다시 실행할 수 있다.
다만 start는 기본으로 detached 모드가 디폴트이고 run은 attached 모드가 디폴트이다.
docker run -d
-d
: detached 모드
docker attach $CONTAINER_NAME
detached 모드를 attach 모드로 변경할 수 있다
docker logs $CONTAINER_NAME
컨테이너에 출력된 과거의 로그를 볼 수 있다
-f
: 컨테이너와 다시 연결해서 향후 출력할 로그도 보여준다
docker start -a
-a
: attach 모드
docker run -it
-i
: 인터렉티브 모드로 실행
-t
: pseudo-TTY 터미널을 생성한다
-it
를 같이 입력하면 입력도 가능하고 컨테이너에 노출되는 터미널도 얻게 된다
도커를 다시 시작했을 때 dettach 모드가 되는데 입력받을 방법이 없을까?
docker start -a -i %COMTAINER_NAME
-t옵션은 유지되기 때문에 화면에 출력되는 -a 옵션과 인터렉티브 모드로 실행하는 -i옵션을 적어줘야 한다.
docker rm $CONTAINER_NAME
컨테이너를 제거한다
docker images
이미지 리스트
docker rmi $IMAGE_ID
이미지 삭제
컨테이너가 삭제된 상태여야 이미지를 삭제할 수 있다 (실행, 중지 상태면 안 됨)
현재 실행 중인 컨테이너에서 사용되지 않는 이미지를 삭제한다
docker image prune
docker run -p 3000:80 -d --rm $IMAGE_ID
--rm
: 컨테이너가 종료될 때 자동으로 제거되는 플래그
이미지는 용량이 크지만 실행중인 컨테이너는 용량이 그렇게 크지 않다.
명령 레이어가 이미지 위에 추가된 얇은 부가 레이어이다.
그래서 이미지 코드는 실행 중인 컨테이너에서 사용된다.
컨테이너는 이미지를 기반으로 빌드되고, 동일한 이미지를 기반으로 실행되는 여러 컨테이너는 이미지 내부의 코드를 공유한다. 그래서 이미지 내부의 코드는 잠겨있다.
docker image inspect $IMAGE_ID
이미지 정보를 출력한다.
컨테이너가 이미 실행 중인 상태에서 무언가를 추출하고 싶다면 어떻게 해야 할까?
docker cp
실행 중인 컨테이너로 또는 실행 중인 컨테이너 밖으로 파일 또는 폴더를 복사할 수 있다.
docker cp dummy/. CONTAINER_NAME:/test`: 옮길 위치, 컨테이너의 /test 하위에 파일을 복사한다
docker cp $CONTAINER_NAME:/test dummy
위치를 반대로 하면 컨테이너에 있는 파일을 로컬로 복사한다
docker run -p 3000:80 -d --rm --name goalisapp $IMAGE_ID
--name
: 컨테이너 이름을 지정할 수 있다
이미지 태그는 name:tag
형식으로 되어 있다
node:14에서 버전 14가 태그이다
docker build -t goals:latest .
-t
: 이미지 태그를 받을 수 있다
docker push $IMAGE_NAME
docker pull $IMAGE_NAME
docker push 계정/도커허브레포:태그이름
다만 로컬 이미지 이름을 계정/도커허브레포:태그이름
로 지정해야 한다
계정/도커허브레포:태그이름
.계정/도커허브레포:태그이름
docker login
도커허브에 로그인할 수 있다
주의사항
도커 pull을 하면 로컬에 있는지 확인한 후 없으면 이미지를 받아온다.
만약 로컬에 이전 버전이 이미 있다면 이전 버전을 사용한다.
FROM node:14
WORKDIR /app
COPY package.json
RUN npm install
COPY . .
EXPOSE 80
CMD ["node", "server.js"]
도커 파일로 이미지 빌드
docker build -t feedback-node .
feedback-node 이미지로 컨테이너 실행
docker run -p 3000:80 -d --name feedback-app --rm feedback-node
도커 중지시 --rm 옵션으로 자동으로 삭제된다
docker stop feedback-app
컨테이너는 파일을 생성할 때 파일을 이미지에 쓰지 않는다.
대신 상단에 추가되는 자체 read-write 레이어에 저장한다
볼륨은 호스트 머신의 하드 드라이브가 컨테이너로 매핑된다.
RUN npm install
COPY . .
EXPOSE 80
VOLUME ["/app/feedback"] // 첫번째 인자: 컨테이너 내부의 경로
CMD ["node", "server.js"]
호스트 컴퓨터의 경로를 지정하지 않았을 떄 도커는 익명 볼륨을 지정한다.
도커가 관리하는 임의의 장소에 있지 사용자는 그 폴더가 어디있는지 모른다.
폴더에 따로 엑세스할 수도 없다.
docker volume ls
도커가 관리중인 모든 볼륨을 리스팅한다
익명 볼륨은 컨테이너가 중지되면 사라진다! -> 데이터를 유지할 수 없다
영구적이어야 하는 데이터이고 편집하거나 직접 볼 필요가 없는 중요한 데이터에 적합하다.
마찬가지로 도커가 관리하는 데이터이며 실질적으로 호스트 머신의 폴더에 엑세스하지 않을 것이기 때문
도커 파일에 작성하는 건 아니고 컨테이너를 실행할 때 생성해야 한다
docker run -p 3000:80 -d --rm --name feedback-app -v feedback:/app/feedback feedback-node
-v feedback:/app/feedback
: 컨테이너에 볼륨을 추가할 수 있다
볼륨의 이름은 feedback이고 /app/feedback을 볼륨에 저장한다
궁금한 점: named volume에 여러 컨테이너가 접근할 수 있을까? - 가능하다
--rm
옵션 없이 컨테이너를 시작하면 docker rm
으로 제거해도 익명 볼륨이 제거되지 않는다.
그래도 컨테이너를 다시 실행하면 새 볼륨이 생성된다.
docker volume prune
호스트 머신의 경로를 직접 설정할 수 있다.
docker run -p 3000:80 -d --rm --name feedback-app -v /Users/mimseong/Document/udemy:/app/feedback feedback-node
-v "/Users/mimseong/Document/udemy:/app"
: :
앞에 절대 경로를 적어준다. 상대 경로가 아님에 유의하자
전체 경로를 복사하고 싶지 않다면 -v $(pwd):/app
로 사용
위와 같이 했을 때 문제가 발생할 수 있다.
RUN npm install
COPY . .
npm install한 뒤 COPY . .를 하면 현재 마운트 되어 있는 /app을 다시 덮어써버린다.
그래서 install한게 날라가버린다.
docker run -p 3000:80 -d --rm --name feedback-app -v "/Users/mimseong/Document/udemy:/app" -v /app/node_modules feedback-node
익명 볼륨을 하나 더 추가한다 -v /app/node_modules
앞의 볼륨은 /app이고 뒤의 볼륨은 /app/node_modules이다.
도커는 더 구체적인 경로를 선택한다. 그래서 뒤의 경로가 살아남아서 /app과 공존한다
nodemon
nodemon sercer.js
노드몬을 사용하면 코드가 변경됐을 경우 서버를 다시 로드한다.
기본적인 권한은 read-write라 컨테이너가 볼륨에 데이터를 쓸 수 있다.
-v "/Users/mimseong/Document/udemy:/app:ro"
: 끝에 :ro
를 붙여서 읽기 권한만 부여할 수 있다.
docker volumn create asdf
asdf라는 볼륨을 만들 수 있다
inspect으로 상세한 정보를 볼 수 있다.
Mountpoint로 마운트 위치를 볼 수 있는데, 이는 실제 경로가 아니라 도커 가상머신의 경로라 찾기 어렵다.
가능하긴 하다! 하지만 개발용 말고 배포할 때는 코드를 마운트 해서 쓸 수 없다.
도커파일에 COPY . .를 해놓고 개발 중일 때 -v로
COPY 명령으로 복사해서는 안 되는 폴더와 파일을 지정할 수 있다.
# .dockerignore
node_modules
.git
ARG
# Dockerfile
ENV PORT 80
EXPOSE $PORT
docker run -p 3000:8000 --env PORT=8000 --env ASDF=1234
-e PORT=8000
: 환경변수를 추가할 수 있다
# .env
PORT=8000
docker run -p 3000:8000 --env-file ./.env
--env-file
: 환경변수 파일을 지정할 수 있다
# Dockerfile
ARG DEFAULT_PORT=80
ENV PORT DEFAULT_PORT
EXPOSE $PORT
docker build -t feedback-node:dev --build-arg DEFAULT_PORT=8000
네트워킹 방법
1. WWW 인터넷으로 통신하는 방법
2. 호스트 머신으로 통신하는 방법
3. 다른 컨테이너로 통신하는 방법
기본적으로 컨테이너는 월드 와이드 웹에 요청을 보낼 수 있다.
도커에서 호스트 머신으로 요청을 할 수 없는데 그럴 경우 localhost
대신 host.docker.internal
을 사용해야 한다.
host.docker.internal
는 도커 컨테이너 내부에서 알 수 있는 호스트 머신의 IP 주소로 변환된다.
docker container inspect 명령어로 몽고디비 컨테이너를 검사한다.
NetworkSettings에 IPAddress가 컨테이너의 IP 주소이다.
이제 이 IP를 적어주면 되는데, 아시다시피 아주 번거로운 방법이다! 그래서 다른 방법을 추천한다 ↓
docker run 명령에 --network 옵션을 추가하면 모든 컨테이너를 하나의 네트워크로 묶을 수 있다.
docker network create favorites-net
볼륨과 달리 네트워크는 직접 만들어줘야 한다
docker run -d --name mongodb --network favorites-net mongo
두 컨테이너가 동일한 네트워크의 일부분인 경우 다른 컨테이너 이름을 적을 수 있다 mysql:27017
-p
옵션은 로컬 호스트 머신이나 컨테이너 네트워크 외부에서 컨테이너에 연결할 때 사용한다.
몽고디비 컨테이너에 연결되는 유일한 것은 favorite 컨테이너이며, 이는 동일한 도커 네트워크의 일부분이 된다.
컨테이너 간에 연결이 있자면 포트를 설정할 필요가 없다.
몽고DB 도커로 띄우기
docker run --name mongodb --rm -d -p 27017:27017 mongo
백엔드 도커로 띄우기
From node:14
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 80
CMD ["node", "app.js"]
도커 이미지 빌드
docker build -t goals-node .
도커 컨테이너 띄우기
docker run -p 80:80 --name goals-backend --rm goals-node
프론트엔드 도커로 띄우기
FROM node
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
도커 이미지 빌드
docker build -t goals-react .
도커 컨테이너 띄우기
docker run -p 3000:3000 --rm --name --it goals-frontend goals-react
--it
를 넣어야만 리엑트가 계속 실행이 된다
네트워크 생성
docker network create goals-net
같은 네트워크로 통신하게 네트워크를 추가했다
docker run --name mongodb --rm -d --network goals-net mongo
docker run --network goals-net -p 80:80 --name goals-backend --rm goals-node
docker run -p 3000:3000 --rm --name --it goals-frontend goals-react
localhost:27017
-> mongodb:27017
로컬호스트 대신 컨테이너 이름을 적어줘야한다
리엑트는 도커 컨테이너가 아닌 브라우저에서 실행되기 때문에 localhost로 적어야 하고, 백엔드틑 80번 포트를 열어놔야 한다.
백엔드와 상호작용 하는 부분은 컨테이너가 아닌 브라우저에서 돌아가므로 네트워크가 필요 없다.
몽고디비 도커 문서를 보면 컨테이너 내부에 파일을 저장하는 곳을 알려준다. /data/db
docker run --name mongodb -v data:/data/db --rm -d --network goals-net mongo
named volume을 사용해서 컨테이너가 죽더라고 데이터를 살려놓을 수 있다.
마찬가지로 몽고디비 문서를 보면 사용자 이름과 비밀번호를 지정하는 방법을 알 수 있다.
-e MONGO_INITDB_ROOT_USERNAME=max -e MONGO_INITDB_ROOT_PASSWORD=secert
환경변수로 지정할 수 있다.
docker run --network goals-net -p 80:80 --name goals-backend -v /Users/mimseong/..../backend:/app -v logs:/app/logs -v /app/node_modules --rm goals-node
-v /Users/mimseong/..../backend:/app
: 폴더 바인딩
-v logs:/app/logs
: named volume 생성
-v /app/node_modules
: anonymous volume
/app/logs
가 /Users/mimseong/..../backend:/app
에 바운딩 되더라도 모르는 동일 폴더에 덮여쓰여지지 않는다.
더 구체적인 경로를 컨테이너에 명시했기 때문.
node_modules는 컨테이너 통틀어 하나만 있으면 되기 때문에, 익명 볼륨으로 생성하면 좋다.
폴더 바인딩을 해서 코드가 실시간으로 반영된다.
# Dockerfile
FROM node
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 80
ENV MONGODB_USERNAME=root
ENV MONGODB_PASSWORD=secret
CMD ["npm", "start"]
mongodb://{process.env.MONGODB_PASSWORD}@mongodb:27017
환경변수로 관리할 수 있다.
docker run --network goals-net -p 80:80 --name goals-backend -v /Users/mimseong/..../backend:/app -v logs:/app/logs -v /app/node_modules -e MONGODB_USERNAME=max -e MONGODB_PASSWORD=password --rm goals-node
COPY로 복사하고 싶지 않은 경우 .dockerignore로 설정할 수 있다.
# .dockerignore
node_modules
Dockerfile
.git
node_modules: 모든 종속성을 불필요하게 다시 복사하지 않도록 하자
Dockerfile: default 환경변수들이 적혀있다. 조심!
docker build
와 docker run
명령을 대체할 수 있는 도구이다.
다중 컨테이너 설정을 더 쉽게 관리할 수 있게 해준다.
도커 컴포즈는 하나의 호스트에서 다중 컨테이너를 관리하는데 적합하다
서비스: 컨테이너
# docker-compose.yaml
version: "3.8"
도커 컴포즈의 어떤 버전을 사용할 것인가 명시
# docker-compose.yaml
version: "3.8"
services:
mongodb:
backend:
frontend:
서비스 하위에 컨테이너의 이름을 명시한다. mongodb, backend, frontend 세 개의 컨테이너를 구동한다.
version: "3.8"
services:
mongodb:
image: 'mongo'
backend:
fromtend:
이미지 이름을 mongo로 한다.
도커는 mongodb 컨테이너가 mongo 이미지를 기반으로 해야 한다는 걸 안다.
mongo는 이미지 이름일 뿐이며, 로컬이나 도커 허브에서 조회된다.
도커 컴포즈는 --rm, -d가 기본 세팅이다.
version: "3.8"
services:
mongodb:
image: 'mongo'
volumes:
- data:/data/db
backend:
fromtend:
볼륨은 -
와 함께 볼륨을 적어주면 된다.
version: "3.8"
services:
mongodb:
image: 'mongo'
volumes:
- data:/data/db
enviroment:
# MONGODB_USERNAME: max
- MONGODB_USERNAME=max
- MONGODB_PASSWORD=password
backend:
fromtend:
환경변수를 설정하는 방법이다.
-를 붙이는 방법, 안 붙이는 방법 두 가지가 있다.
version: "3.8"
services:
mongodb:
image: 'mongo'
volumes:
- data:/data/db
env_file:
- ./env/mongo.env
backend:
fromtend:
환경변수를 파일로 관리할 수 있다.
네트워크는?
하나의 동일한 컴포즈 파일에 정의된 모든 서비스는 동일한 네트워크의 일부가 된다.
version: "3.8"
services:
mongodb:
image: 'mongo'
volumes:
- data:/data/db
env_file:
- ./env/mongo.env
networks:
- goals_net
backend:
fromtend:
따로 설정하고 싶다면 이렇게 설정할 수 있다.
mongodb 서비스는 도커에 의해 자동 생성된 디폴트 네트워크 뿐만 아니라 파일에 지정된 특정 네트워크(goals_net)에도 추가된다.
version: "3.8"
services:
mongodb:
image: 'mongo'
volumes:
- data:/data/db
env_file:
- ./env/mongo.env
backend:
fromtend:
volumes:
data:
volumes:
하위에는 services:
에서 사용중인 명명된 볼륨이 나열되어야 한다.
익명 볼륨과 바인드 마운트는 여기에 지정할 필요가 없다.
docker-compose up
이미지를 가져와 빌드하고 컨테이너를 시작한다
네트워크는 프로젝트 이름 + default로 자동 설정된다.
볼륨도 프로젝트 이름 + data(아까 설정한 named volume)으로 설정된다.
기본적으로 attach 모드로 실행돼서 docker-compose up -d로 dettached 모드로 실행할 수 있다.
docker-compose down
모든 서비스를 중지하고 모든 컨테이너를 제거한다
docker-compose down -v
볼륨까지 삭제한다
version: "3.8"
services:
mongodb:
backend:
image: 'goals-node'
fromtend:
volumes:
data:
이미지는 goals-node로 설정한다.
완성된 이미지를 제공하는 대신 도커 컴포즈에 이미지를 빌드하는데 필요한 모든 정보를 제공할 수도 있다.
version: "3.8"
services:
mongodb:
backend:
image: 'goals-node'
# build: ./backend
build:
context: ./backend
dockerfile: Dockerfile
fromtend:
volumes:
data:
build 옵션은 Dockerfile을 찾을 수 있는 경로를 적어야 한다.
context, dockerfile로 조금 더 자세히 적을 수 있다.
context는 도커 파일의 경로, dockerfile은 도커 파일의 이름을 표시한다.
context는 도커파일이 빌드되는 장소이기도 하다. 이미지가 생성되는 위치이다.
복사할 폴더를 포함하는 폴더로 설정되어야 한다.
version: "3.8"
services:
mongodb:
backend:
image: 'goals-node'
# build: ./backend
build:
context: ./backend
dockerfile: Dockerfile
args:
buildno: 1
gitcommithash: cdc3b19
fromtend:
volumes:
data:
args 설정도 가능하다
version: "3.8"
services:
mongodb:
backend:
build: ./backend
ports:
- '3000:80'
fromtend:
volumes:
data:
ports
옵션으로 포트를 지정 가능하다.
만약 여러 포트를 추가하고 싶으면 -
로 추가하면 된다.
version: "3.8"
services:
mongodb:
backend:
build: ./backend
ports:
- '3000:80'
volumes:
- logs:/app/logs
- ./backend:/app
- /app/node_modules
fromtend:
volumes:
logs:
named volume은 아래 볼륨 하위에 이름을 명시해야 한다.
anonymous volume은 따로 명시할 필요가 없다.
마운트는 도커와 다르게 상대경로를 지정 가능하다!
version: "3.8"
services:
mongodb:
backend:
build: ./backend
ports:
- '3000:80'
volumes:
- logs:/app/logs
- ./backend:/app
- /app/node_modules
env_file:
- ./env/backend.env
fromtend:
volumes:
logs:
환경변수 설정하기
env_file 옵션으로 환경변수 파일로 지정할 수 있다.
version: "3.8"
services:
mongodb:
backend:
build: ./backend
ports:
- '3000:80'
volumes:
- logs:/app/logs
- ./backend:/app
- /app/node_modules
env_file:
- ./env/backend.env
depends_on:
- mongodb
frontend:
volumes:
logs:
depends_on
은 도커 컴포즈에만 있는 옵션이다.
도커 컴포즈를 사용하면 여러 서비스를 만들고 시작한다.
이미 실행되고 있는 다른 컨테이너에 의존할 수 있다.
백엔드는 몽고디비에 의존한다.
depends_on
으로 mongodb
를 먼저 불러와야 한다는 걸 알려줘야한다.
docker-complete_mongodb_1
하지만 도커가 서비스 이름을 기억해서 잘 돌아간다. version: "3.8"
services:
mongodb:
backend:
frontend:
build: ./frontend
ports:
- '3000:3000'
volumes:
- ./frontend/src:/app/src
stdin_open: true
tty: true
volumes:
logs:
--it옵션 대신에
stdin_open
옵션으로 개방형 입력 연결이 필요하다고 도커에게 알린다.
tty
옵션으로 터미널에 연결한다.
위 두 옵션으로 --it 옵션을 나타낼 수 있다.
version: "3.8"
services:
mongodb:
backend:
frontend:
build: ./frontend
ports:
- '3000:3000'
volumes:
- ./frontend/src:/app/src
stdin_open: true
tty: true
depends_on:
- backend
volumes:
logs:
마찬가지로 depends_on으로 종속성을 추가할 수 있다.
docker-compose up --build
--build
옵션을 붙이면 강제로 이미지를 빌드한다. 빌드는 build:
에 정해진 대로 빌드한다.
docker-compose build
도커 이미지만 빌드한다
docker-compose up
도커 이미지를 빌드하고 컨테이너를 띄운다
version: "3.8"
services:
mongodb:
backend:
frontend:
container_name: frontend
volumes:
logs:
서비스 이름이 컨테이너 이름은 아니다.
만약 컨테니어 이름을 따로 지정하고 싶다면 container_name
로 지정한다.
노드를 띄우기 위해서는 노드를 설치해야 하고, package.json을 만들기 위해서는 npm init을 해야 한다. (그러기 위해서도 노드가 필요하다) 호스트에 노드를 설치하지 않고 도커를 활용할 수 있는데 이에 대한 방법을 알아보자.
docker run node
노드를 실행시키자마자 바로 죽어버린다.
왜냐면 노드는 인터렉티브 모드(-it)에서 실행시켜야 하기 때문이다.
docker run -it -d node
detached mode로 컨테이너를 실행한 다음
docker exec -it $CONTAINER_NAME npm init
exec
명령은 컨테이너가 실행하는 기본 명령 외에 실행중인 컨테이너 내에서 특정 명령을 실행할 수 있다.
-it
: 프로세스에 계속 연결되고 입력을 제공하고 싶다면 인터렉티브 모드로 실행해야한다.
docker run -it node npm init
디폴트 명령을 오버라이드 할 수 있다. 컨테이너가 실행된 후 npm init 명령어를 실행한다.
FROM node:14-alpine
WORKDIR /app
유틸리티 컨테이너라 알파인 버전을 사용한다.
alpine version: 초경량 버전
docker build -t node-util .
docker run -it -v 현재경로:/app node-util npm init
변경사항이 자동으로 반영되게 현재 프로젝트를 연결시킨다.
노드등 모든 부가 도구를 설치할 필요가 없다는게 유용하다.
FROM node:14-alpine
WORKDIR /app
ENTRYPOINT [ "npm" ]
ENTRYPOINT는 CMD 명령과 비슷하다.
docker run -it -v 현재경로:/app node-util npm init
docker run에서 이미지 이름 뒤에 명령을 추가하면 npm init
은 CMD 명령을 덮어쓴다.
ENTRYPOINT를 사용하면 이미지 이름 뒤의 npm init 명령을 ENTRYPOINT 뒤에 추가한다!
docker run -it -v 현재경로:/app node-util init
docker run -it -v 현재경로:/app node-util install
이런식으로 npm을 붙이지 않고도 활용 가능하다.
docker run -it -v 현재경로:/app node-util install express --save
express --save
로 명시적 종속성을 설치할 수 있다.
-save
은 명령이 express 패키지를 프로젝트에 대한 종속성으로 추가하도록 허용하는 플래그이다.
version: "3.8"
services:
npm:
build: ./
stdin_open: true
tty: true
volumes:
- ./:/app
docker-compose run npm init
run
: 파일에 여러 서비스가 있는 경우에 단일 서비스만 실행할 수 있다.
run npm init
: npm 서비스를 실행하고 뒤에 실행할 명령 init
을 실행한다.
docker-compose down을 실행할 경우에는 자동으로 컨테이너를 삭제한다.
하지만 docker-compose run은 up과 down이 없다.
docker-compose run --rm npm init
--rm
옵션으로 중지시 컨테이너를 삭제한다
유틸리티 컨테이너로 특정 명령을 실행하는데 사용할 수 있는 환경을 구성할 수 있다.
docker-compose.yaml
version: "3.8"
services:
server:
image: 'nginx:stable-alpine'
ports:
- '8000:80'
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
php:
mysql:
composer:
artisan:
npm:
volume과 path는 nginx 문서에 나와 있는대로 설정한다. 공식문서를 잘 읽자!
server {
listen 80; # 포트 설정
index index.php index.html; # 인덱스 파일 요청 처리
server_name localhost;
root /var/www/html/public; # 들어오는 요청에 응답하는데 사용할 수 있는 파일을 여기서 찾는다
location / { # 모든 수신 요청을 index.php 파일로 리다이렉션하거나
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ { # php 파일을 대상으로 하는 요청이 php 인터프리터로 전달되도록 하는 리다이렉션 규칙
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:3000; # php: 서비스 이름, php 파일을 처리하라는 요청을 3000번 포트로 보낸다
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
php는 공식 이미지를 기반으로 하는 커스텀 이미지를 만든다
-> 도커파일이 필요하다
php 뿐만이 아니라 Laravel이 필요로 한느 몇몇 확장 프로그램도 설치
# php.dockerfile
FROM 'php:7.4-fpm-alpine'
WORKDIR /var/www/html
# 우리가 필요로 하는 php 확장 프로그램
RUN docker-php-ext-install pdo pdo_mysql
끝에 CMD또는 ENTRYPOINT가 없으면 베이스 이미지의 CMD나 ENTRYPOINT를 사용한다.
베이스 이미지는 마지막으로 PHP 인터프리터를 호출한다.
php:
build:
context: ./dockerfiles
dockerfile: php.dockerfile
volumes:
- ./src:/var/www/html:delegated
delegated
: 컨테이너가 일부 데이터를 기록해야 하는 경우에 그에 대한 결과를 호스트 머신에 즉시 반영하지 않고 배치로 기본 처리함으로써 성능이 약간 더 나아진다.
안정성은 떨어지지만, 속도가 향상됨
fastcgi_pass php:3000
nginx를 보면 3000번 포트를 사용하라고 했는데, php의 공식 도커파일을 보면 이미지가 실제로 9000번 포트를 노출한다는 것을 알 수 있다.
그래서 외부 포트는 3000, 내부 포트는 9000을 열어야 한다. -p 3000:9000
하지만~ 실제로는 nginx와 php의 통신이기 때문에 포트를 열어줄 필요가 없다. 그냥 nginx 설정을 9000으로 바꾸자.
fastcgi_pass php:9000;
환경 변수 설정
# mysql.env
MYSQL_DATAVASE=homestead # Laravel의 디폴트 값
MYSQL_USER=homestead
MYSQL_PASSWORD=secret
MYSQL_ROOT_PASSWORD=secret
도커 컴포즈 설정
version: "3.8"
services:
server:
image: 'nginx:stable-alpine'
ports:
- '8000:80'
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
php:
build:
context: ./dockerfiles
dockerfile: php.dockerfile
volumes:
- ./src:/var/www/html:delegated
mysql:
image: mysql:5.7
env_file:
- ./env/mysql.env
composer:
artisan:
npm:
composer는 유틸리티 컨테이너로 사용한다.
처음에 Laravel 애플리케이션을 설정하는데 사용할 수 있다.
docker-compose.yaml
version: "3.8"
services:
server:
image: 'nginx:stable-alpine'
ports:
- '8000:80'
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
php:
build:
context: ./dockerfiles
dockerfile: php.dockerfile
volumes:
- ./src:/var/www/html:delegated
mysql:
image: mysql:5.7
env_file:
- ./env/mysql.env
composer:
build:
context: ./dockerfiles
dockerfile: composer.dockerfile
volumes:
- ./src:/var/www/html
artisan:
npm:
Dockerfile
# composer.dockerfile
FROM 'composer:latest' # 컴포저 이미지가 이미 존재한다!
WORKDIR /var/www/html
# --ignore-platform-reqs 일부 종속성이 누락돼도 경고나 오류 없이 실행할 수 있다
ENTRYPOINT ["composer", "--ignore-platform-reqs"]
composer create-project --prefer-dist laravel/laravel blog
composer를 사용하여 Laravel 프로젝트를 설정할 수 있다
docker-compose run --rm composer create-project --prefer-dist laravel/laravel .
프로젝트가 생성될 폴더를 명령 끝에 특정해야 한다. .
도커파일에 WORKDIR 설정을 /var/www/html로 해서 여기에 Laravel 프로젝트가 생성된다.
server:
image: 'nginx:stable-alpine'
ports:
- '8000:80'
volumes:
- ./src:/var/www/html # php 코드에 접근하기 위해서
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
php 코드에 접근하기 위해서 볼륨을 추가해야 한다.
version: "3.8"
services:
server:
image: 'nginx:stable-alpine'
ports:
- '8000:80'
volumes:
- ./src:/var/www/html # php 코드에 접근하기 위해서
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
/etc/nginx/conf.d/default.conf
더 큰 nginx 구성으로 병합되는 특수 파일이라 할 수 있다.
docker-compose up server php mysql
up 뒤에 서비스 명을 적으면, 해당 서비스만 실행할 수 있다.
depends_on:
- php:
- mysql:
php, mysql와 통신할 수 있을 때 실행 가능하다.
종속성을 추가하면 자동으로 다른 서비스도 실행한다.
docker-compose up server
server의 종속성인 php와 mysql도 실행한다.
추가해야 할 점
php와 같은 커스텀 이미지와 관련이 있다.
현재 docker-compose가 디폴트로 수행하는 작업은 이미지가 있는지 확인하고 이미지가 있으면 그 이미지를 사용한다. 결코 이미지를 리빌드하지 않는다! Dockerfile이 변경되면 docker-compose에 의해 적용되지 않는다. 이미지를 빌드하려면 docker-compose build를 해야 했는데, --build
옵션을 붙이면 변경사항이 있으면 이미지를 다시 빌드한다.
artisan
artisan은 PHP로 빌드된 Laravel 명령이다. 그래서 php가 필요하다 (php Dockerfile 재사용)
artisan:
build:
context: ./dockerfiles
dockerfile: php.dockerfile
volumes:
- ./src:/var/www/html
entryfile: ["php", "/var/www/html/artisan"]
entryfile:
: 도커파일에 아직 ENTRYPOINT가 없는 경우 오버라이드 하거나 추가할 수 있다.
여기서는 Dockerfile 내부에서 수행하는 것처럼 지정한다.
docker-compose run --rm artisan migrate
migrate: Laravel이 지원하는 artisan 명령 중의 하나
데이터베이스에 데이터를 기록한다.
따라서 이 데이터베이스 설정이 작동하는지 그 여부도 확인한다.
npm
npm:
image: node:14
working_dir: /var/www/html
entrypoint: ["npm"]
volumes:
- ./src:/var/www/html
working_dir, entrypoint처럼 도커파일 대신 컴포즈에 작성할 수 있다. (도커파일이 깔끔하긴 함)
docker-compose 파일에는 COPY나 RUN 명령을 사용할 수 없다
바인드 마운트
배포시에는 바인드 마운트를 할 수 없다.
로컬 호스트의 파일 구조가 서버의 파일 구조와 다르기 때문. 똑같은 구조로 파일을 만드는 것도 번거롭다.
우리가 원하는 건 호스트에 있는 파일을 복사해서 이미지를 빌드하는 것이기 때문에 COPY로 해결할 수 있다.
nginx의 바운드 마운트를 COPY로 변경한다.
# nginx.dockerfile
FROM nginx:stable-alpine
WORKDIR /etc/nginx/conf.d
COPY ./nginx/nginx.conf .
RUN mv nginx.conf default.conf
WORKDIR /var/www/html
COPY src .
바운드 마운트를 COPY로 복사함으로써 언제나 소스 코드의 스냅샷을 이미지에 복사하도록 보장한다.
server:
build:
context: ./dockerfiles
dockerfile: nginx.dockerfile
이전 같았으면 이런 식으로 도커파일을 작성했겠지만 실제로 동작하지 않는다.
context는 또한 도커파일이 빌드될 폴더를 설정한다.
nginx.dockerfile은 내부네 nginx, src 폴더를 참조하고 있다.
따라서 dockerfiles를 context로 설정한다면 ./dockerfile 폴더에 빌드 되는데 nginx와 src에는 접근할 수 없어서 이미지 빌드가 실패한다.
server:
build:
context: .
dockerfile: dockerfiles/nginx.dockerfile
그래서 이때는 context를 .으로 설정하고 도커파일 경로를 지정해준다.
php
RUN chown -R www-data:www-data /var/www/html
-R
: 폴더 안의 모든 폴더와 파일에 대해 재귀적으로 수행
www-data
: php 이미지에 의해 생성된 디폴트 사용자