[WEB] 도커 101



#1. 개념을 짚고 갑시다.

컨테이너 이미지

  • OS의 컨테이너 내부에서 애플리케이션을 실행하기 위해 필요한 모든 파일을 캡슐화한 바이너리 패키지
  • 간단히 압축 파일과 같다.
  • 바이너리 파일
  • 고유 id 를 가지며, 컨테이너를 실행하는데 필요한 모든 것을 담고 있다.
  • 컨테이너 실행에 필요한 모든 것이 담긴 단일 파일


도커

  • 가장 famous한 컨테이너 이미지 포맷 === 도커 이미지 포맷
  • 도커 파일을 사용해 도커 컨테이너 이미지를 자동으로 생성할 수 있음
  • 도커 파일 : 컨테이너를 빌드하는 명령어가 들어 있는 텍스트 파일
  • 파일 형식 Dockerfile


컨테이너 생성 및 실행 명령어

  • CMD 방식
    • 이미지를 바탕으로 생성된 컨테이너 실행 시 매번 명령을 실행
    • 해당 명령으로 컨테이너가 실행됨
    • 하나의 CMD 만 작성 가능하며, 중복일 경우 맨 마지막 하나만 유효
  • ENTRYPOINT 방식
    • 도커파일에서 빌드한 이미지를 컨테이너로 생성할 때 단 한번만 실행
    • m1 환경에서 이미지 빌드시, 멀티os를 환경에 맞춰진 이미지를 위해서는 해당 명령어를 사용해야함)


도커 허브 유의점

  • 도커 허브에서 이미지 불러올 시, 오피셜 태그 붙은 것만 사용하기!
    • 이미지는 가벼울수록 좋으니 경량 리눅스를 사용하기
      • 데비안 (devian)
      • 알파인



#2. 도커 파일 작성하기

뭅스터를 이미지로 만드는 실습을 진행해보자


  1. 클라이언트 소스코드를 클론해서 의존성 파일 설치 후, 실행시키는 이미지
// Dockerfile
FROM node:14.20.0-bullseye-slim
RUN apt-get update -y
RUN apt-get install -y git
RUN git clone https://github.com/movester/movester_client_product
ADD .env /movester_client_product

WORKDIR /movester_client_product

RUN npm install
RUN npm install @material-ui/core
EXPOSE 7001

CMD ["npm","run","start"]
  • 노드 이미지를 가져옴 (도커 허브로부터, 경량화된 리눅스인 bullseye)
  • apt 업데이트 진행
  • 깃 설치
  • 깃 클론해옴 (public 레파지토리여서 인증 필요없지만, private 레파지토리라면 사전에 깃 계정 인증하는 코드 추가해야함)
  • .env 파일 copy함 (해당 이미지를 작성중인 파일과 동일한 위치의 .env 파일을 /movester_client_product 내에 복사함
    • .env 파일은 깃 클론시 받아올 수 없기 때문에, 도커 파일 작성하는 환경에서 주입해주어야함
    • 단! 이렇게 작성하면 이미지 안에 .env 를 가지고 있게 됨
    • 컨테이너를 띄운 뒤, 컨테이너로 직접 들어가며 .env 가 노출됨 → 지양해야함!
    • k8s에서는 secret 으로 관리할 수 있음
  • 디렉토리를 이동 (cd 명령어대신 해당 명령어를 사용해야 함)
  • 의존성을 위해 npm 설치
  • 7001 포트를 노출시킴
  • 해당 명령어와 함께 컨테이너가 실행됨


  1. 도커 이미지를 빌드하자

    • docker build -t [image이름] .
    • 해당 작업 파일에는 .env 가 존재해야 한다!

    Untitled


  1. 이미지가 만들어졌는지 확인하자

    • docker images

    Untitled


  1. 컨테이너를 실행시켜보자

    • docker run —name [컨테이너이름] -itd -p [외부포트:내부포트] [이미지이름]
    • docker ps -a 로 실행중인 컨테이너를 확인하자
      • 포트도 7001 로 연결된 걸 확인할 수 있다!

    Untitled

    Untitled


  1. 컨테이너의 내부로 직접 들어가보기

    • docker exec -it [컨테이너이름] sh
      • .env 가 잘 복사됐는지 확인해보자
      • 나갈때는 exit

    Untitled


  1. 컨테이너 로그 확인하기

    • docker logs -f [컨테이너이름]

    Untitled


  1. 컨테이너 상세 정보 확인하기
    • docker inspect [컨테이너이름]


  1. 컨테이너 내리기
    • docker stop [컨테이너이름]
    • 컨테이너를 내려도 볼륨은 남는다! 볼륨까지 깔끔하기 지우자 docker rm $(docker ps -qa)


  1. 사용하지 않는 이미지 지우기
    • docker rmi $(docker images -qa)



#3. 여러 컨테이너를 하나의 네트워크로 묶기 (심화과정)

자! 이제 뭅스터에 필요한 FE, BE, redis 까지 3가지의 설정을 모두 해보자!

// BE Dockerfile
FROM node:lts
RUN apt-get update -y
RUN apt-get install -y git
RUN git clone https://github.com/movester/movester_server_product.git
ADD .env /movester_server_product

WORKDIR /movester_server_product

RUN npm install
EXPOSE 8000

CMD ["npm","run","start"]
  • 여기서는 경량리눅스가 아닌 일반 노드를 가져왔다.

    • 경량 리눅스에는 파이썬이 설치되어 있지 않기때문! (서버 코드에는 bcrypt 가 사용되는데 해당 라이브러리는 파이썬이 있어야한다.)

    docker에서 bcrypt 사용하기

해당 이미지를 빌드하고, 컨테이너에 올려보자 (참고로 redis 컨테이너는 이미 띄어져있다.)

Untitled

앗…! 실행이 안된다?

Untitled

백앤드 컨테이너의 로그를 조회하면, 다음과 같이 레디스 연결이 안된 것을 확인할 수 있다.

흐음…좀 더 디버깅을 해보자

be 컨테이너 내부로 접속하여 telnet 을 설치한 후, ping을 해보았다.

  • apt-get update -y && apt-get install telnet -y
  • telnet redis 6379

Untitled

  • 역시 서로 네트워크 연결이 안되어있다. 왜그럴까?


컨테이너를 동일 네트워크로 묶기

이유는 소제목으로 짐작 가능하다시피, 각각 컨테이너의 네트워크가 동일하지 않기 때문이다.

네트워크를 생성하여 해당 컨테이너를 동일 네트워크로 묶어주자


  1. 뭅스터 네트워크 생성
    • docker network create [네트워크 이름]
    • 네트워크 리스트 확인 docker network ls
    • 컨테이너를 해당 네트워크로 띄우기 (fe, be, redis 세개 모두 동일 네트워크에 작업해야한다) docker run —name [컨테이너이름] -itd —networkt [네트워크이름] -p [포트:포트] [이미지이름]

Untitled

Untitled


  • 네트워크에 잘 묶였는지 확인해보자

    docker network inspect [네트워크이름]

Untitled



BE-REDIS 접속

자. 이제 be와 redis 의 접속을 확인해보자!

be 컨테이너 접속 후 telnet redis를 확인해보자

Untitled

연결이 잘 됐다! 이때 172.29.0.2 ip를 기억하자!

  • telnet 세션 접속 나가기 control + ]
  • telnet 나가기 q 누른 뒤 엔터

  • 이때 redis는 엄밀히 말하면 localhost 가 아닌 private ip인 172.29.0.2 를 가지고 있기 때문에 be 코드에서 redis에 접근했던 host 를 변경해주어야 한다.
  • 해당 ip는 컨테이너마다 유동적으로 변한다! k8s 에서는 service를 사용하여 도메인네임을 지정할 수 있다.
// .env
// as-is
REDIS_HOST = "127.0.0.1";
// to-be
REDIS_HOST = "172.29.0.2";
  • be 컨테이너 내부로 직접 들어가서 .env 를 변경한 후 재실행해보자!

    • 이때 우리의 컨테이너에는 vim이 없기때문에 vim을 한번 설치해주자
    • apt-get install vim -y
    • vi .env
  • 잘 연결이 되었다!🤭

Untitled

  • api 응답도 잘 온다

Untitled



FE-BE 접속

이제 fe-be 통신을 연결해주자!

마찬가지로 기존의 로컬호스트를 바라보던 api 호출 host를 연결된 네트워크 내의 private ip 로 변경해주어야 한다.

// fe
// src/services/defaultClient.js
baseURL: '/api',
// .env
// as-is
REACT_APP_SERVER_HOST = "http://127.0.0.1:8000";
// to-be
REACT_APP_SERVER_HOST = "http://[서버 컨테이너 ip]:[서버포트]";


fe 컨테이너를 restart 해주자!

Untitled

접속이 잘 된다!

  • 디버깅까지 해보자
    • fe 컨테이너 내부 접속 후 curl 요청을 해보자
    • api-get install curl -y
    • curl [http://172.29.0.3:8000/api/weeks/expose](http://172.29.0.3:8000/api/weeks/expose)

Untitled

  • 역시 잘 연결되어진 것을 확인할 수 있다!👍

Untitled

이로써 3개의 컨테이너를 띄우고 컨테이너들을 하나의 네트워크로 연결하였다🎉



한 가지 의문점 🤔

// fe
// src/services/defaultClient.js
baseURL: `http://${process.env.REACT_APP_SERVER_HOST}/api`,
REACT_APP_SERVER_HOST = "[서버 컨테이너 ip]:[서버포트]";

이렇게 작업을 하면 api 호출을 컨테이너 네트워크 내부의 private ip로 던지지 않고, 외부 public ip ( = 브라우저 검색창 GET 요청)로 요청이 되어 제대로 api 요청이 이루어지지 않는다. 🤔



#4. ECR 에 업로드하기

k8s 와 연동하기 위해 ECR에 이미지를 업로드하자!

유의점은 이미지 빌드 환경이 현재 M1 이기 때문에 multi os 를 지원하는 이미지를 생성해야 한다.

  • entrypoint 방식을 사용해야 한다.
  • 빌드 명령어 docker buildx build --platform linux/amd64 -t [image이름] .
// Dockerfile
FROM node:14.20.0-bullseye-slim
RUN apt-get update -y
RUN apt-get install -y git
RUN git clone https://github.com/movester/movester_client_product

WORKDIR /movester_client_product
ADD entrypoint.sh ./

RUN npm install
RUN npm install @material-ui/core
RUN chmod 755 entrypoint.sh
EXPOSE 7001

ENTRYPOINT ["./entrypoint.sh"]
// entrypoint.sh
#!/bin/bash
npm run start


ECR push 인증하기

 aws ecr get-login-password --region ap-northeast-2 --profile dev | docker login --username AWS --password-stdin [ecr 이미지 url]


참고자료

Docker - Dockerfile 작성 / Build (이미지 제작)

Docker 네트워크 사용법