도커(Docker) 알아보기

간단한 역사

도커에 대한 검색을 하면 흔히 나오는 사진이다.

 

1. Tradition Deployment:

 물리적인 컴퓨터 한 대(Hardware)에 하나의 OS를 깔고 그 위에 여러가지 프로그램을 설치하는 방식이다. 한 대의 컴퓨터에서 여러 프로그램을 수행하다 보니 퍼포먼스 저하, 또는 프로그램 충돌이 일어날 수 있다.

 

2. Virtualized Deployment:

 VMware, VirtualBox 등.. 리눅스쓸때 지겹게 썼던 그것이다. 1번 방법으로 문제를 해결하기 위해 여러개의 물리적 컴퓨터를 이용하는 방법 대신, Hypervisor를 사용하여 하나의 시스템위에 여러개의 가상 컴퓨터를 올려 구동한다. 각각의 가상머신은 논리적 컴퓨터이므로, CPU, 메모리, 저장장치를 각각 할당할 수 있다 (물론 Hardware의 리소스를 잡아먹는다). 이 방법을 사용하면 가상머신 각각의 성능을 임의로 조절할 수 있고, 여러개의 프로그램을 각각의 가상화된 컴퓨터 위에서 프로그램을 실행시키는 방법으로 프로그램을 분리할 수 있다.

 1번 방법의 한계를 해결할 수 있으나, 무거운 가상 컴퓨터를 여러개 사용하기 때문에 여러개의 OS와 CPU, 메모리 등을 할당해주어야 하기 때문에 무거운 편이다.

 

3. Container Deployment

 2번 방법에서 Hypervisor를 통해서 여러개의 가상 컴퓨터를 올려서 구동하는 대신, Container Runtime를 사용한다. 컨테이너와 이전 가상머신의 가장 큰 차이점은 프로그램 구동을 위해서 별도의 OS를 설치할 필요가 없으며, 모든 컨테이너가 같은 OS를 공유한다는 것이다. 하지만 전통적 배포 방법과는 다르게 프로그램 사이에 간섭을 일으키지 못하도록 제한하며 독립적인 자원을 사용할 수 있도록 할당해주고 관리한다는 차이를 가진다.

 이러한 것을 가능하게 하는 Container Runtime 중 가장 유명한 것이 도커(Docker)이다.

 

 

 

 

 

 

도커란?

 

 리눅스 컨테이너를 다루는 도구(컨테이너 런타임) 중 하나이다. VM과 비슷한 역할을 하지만 OS단위로 가상화하지 않으며, App단위로 쪼갠다는 특징이 있다. (virutualbox나 vmware workstation같은 기존의 VM은 하나의 호스트 아래에서 여러개의 운영체제를 생성하여 가동하는 형식이었다.)

기존 VM위에 올라간 운영체제 각각은 base OS나 다른 VM과는 별도로 동작하며, 완전히 독립된 공간에서 사용되기 때문에 각각이 완전히 분리된 환경에서 실행되며, 가상머신 각각의 이미지 사이즈가 매우 크다. 반면 도커는 앱 단위로 컨테이너라는 작은 규모로 가상화하기 때문에 기존의 VM보다 작은 크기를 가진다.

 

 

 

도커 이미지란?

 

 도커 이미지란 서비스 운영에 필요한 다양한 서버 프로그램, 소스코드 및 라이브러리, 실행파일을 묶어놓은 읽기 전용 스냅샷을 말하며, 실행 중인 이미지를 컨테이너라고 한다. 간단히 말해 특정 프로세스를 실행하기 위한 모든 라이브러리와 설정값, 파일들을 모아놓은 것을 의미한다.

 

이러한 이미지들은 단순히 템플릿일 뿐이므로 상태값을 갖지 않고, 하나의 이미지로부터 여러 개의 컨테이너가 생성될 수 있으며, 컨테이너가 삭제되더라도 이미지는 변하지 않고 남아있다.

 

 

 

 

도커 컨테이너란?

 

 일반적으로 앱이 구동되는 환경까지 감싸 실행할 수 있도록 하는 격리 기술을 컨테이너라고 하는데, 도커에서 실행 중인 이미지를 컨테이너라고 한다. 도커 엔진 위의 논리적인 공간을 이야기하며, 이러한 논리적인 공간(=컨테이너)을 만들어 다양한 어플리케이션을 설치하여 하나의 서버처럼 사용할 수 있게 된다. 하나의 컨테이너 안에 다양한 어플리케이션을 담고, 컨테이너에 접근하여 그 안의 어플리케이션을 사용할 수 있다.

 하드웨어 가상화를 제공하는 VM과 다르게 도커는 다른 커널을 공유하는 여러 컨테이너를 가지는데, 개발환경 하나를 구축하기 위해서 들어가는 많은 수고들을 최소화하기 위해서 컨테이너라는 환경에 세팅을 마치고 그 컨테이너 환경을 배포함으로서 번거로운 부분을 최소화할 수 있다.

 

docker-desktop에서 컨테이너를 들어가보면 컨테이너마다 이미지 정보가 있다.

 

 

 

Dockerfile

 

 이번엔 도커를 이용하여 스프링부트 프로젝트 이미지를 만들고, 해당 이미지를 바탕으로 컨테이너를 만들어 보도록 하겠다. 대상이 되는 스프링부트 프로젝트는 mysql, redis, mongodb와 연결되어있다.

가장 먼저 할 일은 Dockerfile을 작성하는 일이다. Dockerfile은 도커에 존재하는 이미지를 기반으로 새로운 이미지와 컨테이너를 생성할 수 있게 하는 파일이다. 

 

FROM openjdk:17
ARG JAR_FILE=./build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

(참고: https://docs.docker.com/engine/reference/builder/)

 

 Dockerfile은 반드시 이 From절로 시작해야 한다. 이것은 build시에 기초가 될 Base Image를 지정하는 역할을 한다. 위에서 openjdk:17이라는 것은 JDK 17을 사용한다는 것을 말한다. From절에 Base Image와 태그를 지정하면 도커가 해당 이미지를 가져온다. 

 

 FROM다음에 나오는 ARG명령어는 docker build시 넘길 인자를 정의하기 위해서 사용된다. 예를들어 위의 예시에서는 'ARG JAR_FILE = ./build/libs/*.jar'로 표현되었는데, 이렇게 설정하면 일종의 변수를 설정해두는것같이 ${JAR_FILE}과 같이 사용할 수 있다. 위 예시에서는 (COPY ${JAR_FILE} app.jar과 같이 사용하고 있다.

 

  COPY명령은 호스트 컴퓨터의 디렉터리나 파일을 도커이미지로 복사하기 위해서 사용된다. 위 명령에서는 'COPY ./build/libs/*.jar app.jar'와 같이 사용되어 해당 경로에 있는 jar파일을 도커 이미지에 복사하기 위한 목적으로 사용되었다.

 

 ENTRYPOINT명령은 이미지를 컨테이너로 띄우고 나서 실행되어야 하는 커맨드를 지정하는데, 위에서와 같이 ENTRYPOINT ["java","-jar","/app.jar"]와 같이 사용되면 컨테이너로 올라간 직후 java -jar /app.jar명령을 실행하게 된다.

 

 

마지막 줄처럼 여러 개의 CMD가 있을 경우, 마지막 CMD를 제외한 나머지는 무시된다.

CMD도 동일하게 컨테이너 시작 시 실행될 커맨드를 지정하는 명령어인데, 항상 실행되어야 하는 커맨드를 지정하기 위한 목적으로 사용하는 ENTRYPOINT와 다르게 선택적으로 사용할 수 있다.

 

예를 들어 

ENTRYPOINT ["python"]
CMD ["helloworld.py]

이렇게 Dockerfile을 작성했다고 하자. 

docker run python_image
>> python helloworld.py

이렇게 별도의 인자 없이 실행하게 되면 뒤에 지정한 python helloworld.py와 같이 명령이 실행된다

 

하지만 파라미터에 별도의 인자를 지정하게 되면 Dockerfile에 CMD를 이용해서 정의한 hellworld.py가 아닌 입력한 파라미터를 덧붙여 실행하게 된다.

docker run python_image testworld.py
>> python testworld.py

 

 

FROM openjdk:17
ARG JAR_FILE=./build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

따라서 정리하자면 처음에 나왔던 이 도커파일은 jdk17을 사용하고, ./build/libs/에 있는 jar파일을 app.jar라는 이름으로 컨테이너로 복제한 이후 java -jar /app.jar명령을 통해 실행하라는 것을 말한다.

 

 

 

 

 

Docker-Compose

 

version: '3'
services:
  database-mysql:
    container_name: mysql_container
    image: mysql/mysql-server
    ports:
      - "3306:3306"
    volumes:
      - C:\Users\Home\docker\mysql:/var/lib/mysql
    environment:
      MYSQL_DATABASE: docker_test
      MYSQL_ROOT_HOST: '%'
      MYSQL_ROOT_PASSWORD: 1234
      TZ: 'Asia/Seoul'

  database-redis:
    container_name: redis_container
    image: redis:latest
    ports:
      - "6379:6379"
    volumes:
      - C:\Users\Home\docker\redis:/data

  database-mongo:
    container_name: mongo_container
    image: mongo
    ports:
      - "27017:27017"
    volumes:
      - C:\Users\Home\docker\mongo:/data/db
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=1234
      - MONGO_INITDB_DATABASE=docker_test

  spring-boot:
    container_name: spring-docker-test
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - TZ=Asia/Seoul
      - MYSQL_URL=jdbc:mysql://host.docker.internal:3306/docker_test?serverTimezone=Asia/Seoul
      - MYSQL_USERNAME=root
      - MYSQL_PASSWORD=1234
      - MONGO_HOST=host.docker.internal
      - MONGO_PORT=27017
      - MONGO_URI=mongodb://root:1234@host.docker.internal:27017/docker_test?authSource=admin
      - REDIS_HOST=host.docker.internal
      - REDIS_PORT=6379
    ports:
      - "8080:8080"
    depends_on:
      - database-mongo
      - database-mysql
      - database-redis

 

environment를 통해서 환경변수를 일괄적으로 넘겨줄 수 있다. 

 

위 예시는 docker-compose를 사용하여 한번에 여러개의 컨테이너를 생성하는 예시이다. 이렇게 파일로부터 설정을 읽어들임으로서 하나 이상의 컨테이너로부터 이루어진 서비스를 실행하는 절차를 자동화 할 수 있다. 

 

 

+ 해결법 찾아보기 - ec2에 actions를 사용해서 .jar를 올릴때는 그냥 ec2 리눅스서버의 환경변수를 설정해줌으로서 노출되어서는 안되는 secretKey를 암호화할 수 있었다. 그런데 ec2서버에 도커를 올려서 실행하게 되면 이 경우 ec2에서 docker-compose를 통해서 실행할때 environment로 환경변수를 넘겨줘야 한다. 문제는 docker-compose파일에 적힌 secretKey 자체가 노출된다는 것이다. gitignore해주는 방법이 있기는 한데.. 다른방법이 있을듯?

 

/** 일단 깃헙에서 노출되는 문제는 docker-compose의 environment들을 변수로 관리한다음, 로컬에서는 .env파일을 활용해서 값을 보관한 다음 gitignore하고, 깃헙에서는 actions secrets으로 넘기면 해결할 수 있다. **/

 

 

 

 

DB 연결

 

(이미지 생성한 이후)

 

- springboot, dockerfile

docker build -t docker-spring-test .
docker run springbootapp //컨테이너 직접 생성
docker-compose up //docker-compose 이용

https://ttl-blog.tistory.com/761

https://da2uns2.tistory.com/entry/Docker-%EB%8F%84%EC%BB%A4%EC%97%90-Spring-Boot-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0

 

 

- redis

docker run --name redis-container -v C:\Users\Home\docker\redis:/data/db -p 6379:6379 redis
docker exec -it <컨테이너 ID> redis-cli //redis client

keys *

https://hello-i-t.tistory.com/137

 

 

- mysql

docker run --name mysql-container -p 3306:3306 -v C:\Users\Home\docker\mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=1234 mysql
docker start mysql-container
docker exec -it <컨테이너 ID> bash

mysql -u username -p password

use users;
select * from users;

https://poiemaweb.com/docker-mysql

https://shawn-dev.oopy.io/accessing-local-db-from-docker-container

 

 

- mongodb

docker run --name mongodb-container -v C:\Users\Home\docker\mongo:/data/db -d -p 27017:27017 mongo
docker start mongodb-container
docker exec -it <컨테이너 ID> bash

mongosh -u username -p password

//계정이 없다면
mongosh
use admin
db.createUser(
  {
    user: "username",
    pwd:  "password",
    roles: [
    	{ "role" : "readWriteAnyDatabase", "db" : "admin" },
    	{ "role" : "userAdminAnyDatabase", "db" : "admin" },
        { "role" : "dbAdminAnyDatabase", "db" : "admin" },
        { "role" : "clusterAdmin", "db" : "admin" },
        { "role" : "restore", "db" : "admin" },
        { "role" : "backup", "db" : "admin" }
	]
  }
)

//확인
use admin
show collections
db.system.users.find()

 

이렇게 응답이 오면 성공적으로 계정이 생성된 것.

 

https://poiemaweb.com/docker-mongodb

https://www.bearpooh.com/127

https://www.bearpooh.com/168

 

 

볼륨

https://velog.io/@ette9844/Windows10-%EC%97%90%EC%84%9C-varlibdocker-%EC%B0%BE%EA%B8%B0

 

 

 

 

 

=======================================================================================

 

발생한 다양한 에러들

 

 

1. mysql, mongodb 연결은 성공했는데 redis 동작이 안된다. 처음에는 yml에 오타냈겠지 정도로 생각했는데 실행된 컨테이너 목록을 보니 redis만 Port가 지정되지 않은 것처럼 보였다. 

 

 

지금은 mysql, redis, mongodb 모두 port가 존재하는데 redis만 포트가 없기에 구글링을 통해 아래와 같이 수정했다 (더럽게 안나옴)

https://junior-datalist.tistory.com/351

 

문제 찾는데 엄청 오래걸렸다. 그런데 mysql은 왜 port 설정 안해줬는데도 동작한걸까? 기본으로 설정되는거같긴 한데

 

 

2. mongodb는 초기화 안되고 users테이블 유지되는데 mysql 컨테이너 종료시 데이터가 날아가버린다. 삭제되는 시점이 docker-compose로 재실행하는 시점에 db에서 데이터가 날아가는것이 ddl_auto가 create일때 모습같은데 ddl_auto는 validate였다.

 

-> 컴퓨터 껏다가 켜고나니 해결 (이미지 삭제-실행, 컨테이너 삭제-실행 등으로는 해결되지 않은 문제. 아무래도 컴퓨터를 껐다 키는 과정에서 mysql 컨테이너도 껏다 켜졌는데, 그때 문제가 해결된게 아닌가 싶다. 왜 발생했는지 원인은 찾지 못함)

-> 아무래도 docker-compose 바뀐 코드를 clean build안해줘서 그런듯. build는 수동으로 해주고 도커에 올려줘야 한다.

 

 

3. 로컬에서 돌리면 run은 되는데 run창은 안떴다. 컴퓨터 재부팅, 캐시삭제 등 소용없고 다른 프로젝트에서는 제대로 동작하는 모습을 보아 인텔리제이의 문제도 아닌것 같음.


-> 파일하나 코드한줄씩 지워가면서 뭐가문젠지 테스트하다가 run창을 띄우는 단축키를 찾아서 alt+4 눌러보니 run창이 안떴을뿐 안에서 동작하고 있었음. 버그인것같다.. 수정한 파일을 하나씩 재작업해가면서 어디서 문제가 발생했는지 찾아봤는데 Dockerfile을 생성하고 난 이후부터 run창이 뜨지 않았다. 우연일까?

alt+4 누르니 제대로 나오더라

 

 

 

github: https://github.com/eckrin/docker-kubernetes

 

GitHub - eckrin/docker-kubernetes: docker+kubernetes practice

docker+kubernetes practice. Contribute to eckrin/docker-kubernetes development by creating an account on GitHub.

github.com

 

 

참고: https://docs.docker.com/get-started/

https://kubernetes.io/ko