본문 바로가기
Docker/docs

[Docker][docs] 07. 다중 컨테이너 앱

by NJ94 2023. 12. 8.

 

https://docs.docker.com/get-started/07_multi_container/

 

Multi container apps

Using more than one container in your application

docs.docker.com

 

 

다중 컨테이너

다중 컨테이너 애플리케이션을 설계할 때 MySQL 같은 데이터베이스를 추가하는 경우, 어디에서 MySQL을 실행할지에 대한 질문이 자주 발생합니다. 일반적으로 각 컨테이너는 특정 기능을 수행하도록 설계됩니다. 여러 컨테이너를 사용하는 이유는 다음과 같습니다.

 

  1. 기능별 확장성: 데이터베이스와 API 또는 프론트엔드와 같이 서로 다른 기능은 서로 다르게 확장될 수 있습니다. 데이터베이스는 사용자 수에 관계없이 확장해야 할 수도 있습니다.
  2. 버전 및 업데이트 관리: 각 컨테이너를 별도로 실행하면 개별적으로 업데이트하고 버전을 관리할 수 있습니다. 데이터베이스 엔진이나 다른 부분의 업데이트가 애플리케이션 전체에 영향을 미치지 않도록 합니다.
  3. 로컬 및 운영 환경 분리: 로컬 개발 환경에서는 데이터베이스를 포함한 컨테이너를 사용할 수 있지만, 운영 환경에서는 관리형 데이터베이스 서비스를 사용하여 앱과 데이터베이스를 분리하여 관리할 수 있습니다.
  4. 복잡성 관리: 각 컨테이너는 하나의 프로세스만 실행하도록 설계되었기 때문에, 여러 프로세스를 실행하려면 별도의 프로세스 관리자가 필요합니다. 컨테이너를 분리하면 시작과 종료가 더 복잡해집니다.

이러한 이유로 각각의 컨테이너가 한 가지 역할을 수행하도록 설계하고, 여러 컨테이너를 활용하여 애플리케이션을 구성합니다. 이것은 유연성과 관리 용이성을 제공하여 애플리케이션을 효율적으로 운영하고 확장할 수 있도록 도와줍니다.

 

 

컨테이너 네트워킹

각각의 컨테이너는 기본적으로 격리되어 있어, 동일한 호스트에서 실행 중인 다른 컨테이너나 프로세스에 대해 자동으로 알지 못한다. 그러나 컨테이너 간에 통신하기 위해서는 네트워킹을 설정해야한다.

 

 

MySQL 시작

네트워크에 컨테이너를 배치하는 방법에는 두가지 방법이 존재한다.

 

  • 컨테이너를 시작할 때 네트워크를 할당한다
  • 이미 실행 중인 컨테이너를 네트워크에 연결한다

아래의 예시는 먼저 네트워크를 생성한 다음 시작시 MySQL 컨테이너를 연결한다.

 

1. 네트워크를 생성한다.

$ docker network create todo-app

 

2. MySQL 컨테이너를 시작하고 네트워크에 연결한다. 또한 데이터베이스가 데이터베이스를 초기화하는 데 사용할 몇 가지 환경 변수를 정의한다. 

MySQL Docker Hub:  https://hub.docker.com/_/mysql/?_gl=1*1qcg0of*_ga*MjAwMTU0NTYxNS4xNzAxMTU0NTg5*_ga_XJWPQMJYHQ*MTcwMjAxMDUwNy4xMS4xLjE3MDIwMTA1MDguNTkuMC4w

 

[Window]

$ docker run -d `
    --network todo-app --network-alias mysql `
    -v todo-mysql-data:/var/lib/mysql `
    -e MYSQL_ROOT_PASSWORD=secret `
    -e MYSQL_DATABASE=todos `
    mysql:8.0

 

이전 명령에서 --network-alias플래그를 볼 수 있습니다. 이후 섹션에서는 이 플래그에 대해 자세히 알아봅니다.

todo-mysql-data 위 명령에서 명명 된 볼륨은  /var/lib/mysql MySQL이 데이터를 저장하는 에 마운트되어 있음을 알 수 있습니다 . 그러나  docker volume create 명령을 실행한 적이 없습니다. Docker는 명명된 볼륨을 사용하려는 사용자를 인식하고 자동으로 볼륨을 생성합니다.

 

Docker에서는 컨테이너 내부의 데이터를 영구적으로 저장하기 위해 볼륨(volume)이라는 개념을 사용합니다. 볼륨은 호스트 머신이나 Docker가 관리하는 특별한 디렉토리로, 컨테이너 내부의 데이터를 저장하는 데 사용됩니다.

 

todo-mysql-data라는 명명된 볼륨이 언급된 경우, 이것은 MySQL 데이터베이스가 사용하는 데이터 디렉토리인 /var/lib/mysql과 연결되어 있는 것을 의미합니다. 특이한 점은 사용자가 직접 docker volume create 명령을 실행하지 않았는데도 Docker가 이 볼륨을 자동으로 생성했다는 것입니다.

 

Docker는 일반적으로 컨테이너 설정 또는 명령어에서 볼륨을 명시하지 않아도, 명명된 볼륨을 사용하려는 경우 해당 볼륨이 없으면 자동으로 생성합니다. 따라서 todo-mysql-data라는 볼륨을 언급하고 해당 볼륨을 마운트하는 명령을 사용했을 때, Docker는 이 볼륨을 생성하고 컨테이너 내부의 /var/lib/mysql에 연결합니다.

 

이러한 방식으로 사용자는 명시적으로 볼륨을 생성하지 않고도 Docker에 의해 자동으로 관리되는 볼륨을 사용할 수 있습니다. 이것은 사용자 편의를 위해 도커가 자동으로 관리하는 기능 중 하나입니다.

 

MySQL 자동으로 볼륨 생성

 

3. 데이터베이스가 실행 중인지 확인하려면 데이터베이스를 연결되는지 확인하세요

$ docker exec -it <mysql-container-id> mysql -u root -p

 

비밀번호 프롬프트가 나타나면 를 입력하세요 secret. MySQL 셸에서 데이터베이스를 나열하고 todos데이터베이스가 표시되는지 확인합니다.

mysql> SHOW DATABASES;

 

다음과 같은 출력이 표시됩니다.

+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| todos              |
+--------------------+
5 rows in set (0.00 sec)

 

4. MySQL 셸을 종료하여 컴퓨터의 셸로 돌아갑니다.

mysql> exit

 

이제 데이터베이스가 생겼고 todos사용할 준비가 되었습니다.

 

MySQL에 연결

이제 MySQL이 실행 중임을 알았으므로 사용 할 수 있다. 동일한 네트워크에서 다른 컨테이너를 실행하는 경우 해당 컨테이너를 어떻게 찾을 수 있을까? 각 컨테이너에는 고유한 IP 주소가 있기 떄문에, 고유한 IP 주소를 찾아야 한다.

 

네트워킹 문제를 해결하거나 디버깅하는데 유용한 많은 도구와 함께 제공되는 컨테이너

: https://github.com/nicolaka/netshoot

 

nicolaka/netshoot

네트워크 문제 해결을 위한 도구 모음을 포함한 Docker 이미지입니다. 이 이미지에는 네트워크 디버깅, 테스트 및 분석을 돕기 위한 다양한 도구와 유틸리티가 포함되어 있습니다. 이 도구는 네트워크 관련 문제 해결을 용이하게 하기 위해 다음과 같은 유용한 도구들을 제공합니다:

 

  1. ping - 호스트와 네트워크 장치 간의 연결을 확인하기 위한 테스트 명령어.
  2. ifconfig / ip - 네트워크 인터페이스의 구성을 확인하고 관리하는 데 사용되는 도구.
  3. traceroute / tracepath - 패킷이 네트워크를 통해 전송되는 경로를 확인하는 데 사용되는 도구.
  4. curl / wget - 웹 리소스를 가져오고 확인하기 위한 명령어.
  5. netcat - TCP/IP 네트워크를 통해 데이터를 읽고 쓰는데 사용되는 유틸리티.
  6. nslookup / dig - DNS 서버를 조회하고 도메인 이름을 해석하는 도구.
  7. tcpdump - 네트워크 패킷을 캡처하고 분석하는 데 사용되는 패킷 분석 도구.
  8. nmap - 네트워크 탐지 및 보안 검사 도구.

 

이러한 도구들은 네트워크 문제 해결, 디버깅 또는 네트워크 구성에 대한 이해를 돕기 위해 사용됩니다. nicolaka/netshoot Docker 이미지를 사용하면 이러한 도구들을 효과적으로 활용하여 네트워크 관련 작업을 수행할 수 있습니다.

 

1. nicolaka/netshoot 이미지를 사용하여 새 컨테이너를 시작합니다. 반드시 동일한 네트워크에 연결하세요.

$ docker run -it --network todo-app nicolaka/netshoot

 

2. 컨테이너 내부에서는 dig유용한 DNS 도구인 명령을 사용하게 됩니다. 호스트 이름에 대한 IP 주소를 조회하게 됩니다 mysql.

$ dig mysql

 

아래와 같은 출력이 표시된다.

; <<>> DiG 9.18.8 <<>> mysql
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;mysql.				IN	A

;; ANSWER SECTION:
mysql.			600	IN	A	172.23.0.2

;; Query time: 0 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Tue Oct 01 23:47:24 UTC 2019
;; MSG SIZE  rcvd: 44

 

"답변 섹션"에는 다음으로 해결되는 A레코드가 표시됩니다 (귀하의 IP 주소는 다른 값을 가질 가능성이 높습니다). 일반적으로 유효한 호스트 이름은 아니지만 Docker는 해당 네트워크 별칭이 있는 컨테이너의 IP 주소로 이를 확인할 수 있었습니다. 이전 것을 사용했다는 것을 기억하십시오 .mysql172.23.0.2mysql--network-alias

 

이것이 의미하는 바는 앱이 이름이 지정된 호스트에만 연결하면 mysql데이터베이스와 통신한다는 것입니다.

 

Docker 컨테이너는 기본적으로 각자의 IP 주소를 갖지만, 컨테이너 간에 통신할 때 특정 네트워크 별칭을 사용하여 서로를 식별할 수 있습니다. 위의 설명에서 언급된 별칭인 .mysql172.23.0.2mysql--network-alias은 특정 Docker 네트워크 내에서 MySQL 데이터베이스 컨테이너의 IP 주소를 나타내는 것으로 보입니다.

이 별칭은 해당 Docker 네트워크에 있는 MySQL 데이터베이스 컨테이너를 가리킵니다. 이러한 방식으로 다른 컨테이너(예: 애플리케이션 서버 컨테이너)는 이 별칭을 사용하여 MySQL 데이터베이스 컨테이너에 연결하고, 데이터베이스와 통신할 수 있습니다.

이것은 컨테이너 간의 통신을 단순화하고 유지보수를 쉽게하기 위한 방법 중 하나입니다. 이름이 지정된 호스트처럼 컨테이너를 식별하고 특정 서비스에 접근할 수 있도록 해줍니다.

 

 

A 레코드

DNS(Domain Name System)에서 A 레코드는 주소 레코드(Address Record)로, 호스트의 IPv4 주소를 나타냅니다. 이 레코드는 도메인 이름을 해당 도메인에 대응하는 IP 주소로 매핑합니다.

 

간단히 말하면, A 레코드는 도메인 이름과 해당 도메인이 사용하는 IPv4 주소 사이의 매핑 정보를 가지고 있습니다. 이 레코드를 이용하면 사용자가 도메인 이름을 입력할 때, DNS 서버는 해당 도메인에 연결된 IPv4 주소를 찾아 반환하여 사용자가 목적지 서버에 연결할 수 있게 합니다.

 

예를 들어, "example.com" 도메인이 IP 주소 "192.0.2.1"에 매핑되어 있다면, 이 정보는 A 레코드에 저장되어 있을 것입니다. 사용자가 "example.com"을 입력하면 DNS 서버는 A 레코드를 통해 해당 도메인에 대응되는 IPv4 주소를 반환하여 사용자가 웹 브라우저 등을 통해 해당 서버에 접속할 수 있도록 도와줍니다.

 

A 레코드는 IPv4 주소에 대한 정보를 저장하며, IPv6 주소에 대한 매핑 정보는 AAAA 레코드에 저장됩니다. DNS의 기본적인 동작 메커니즘 중 하나로, 인터넷에서 호스트 이름을 IP 주소로 변환하는 데 사용됩니다.

 
 
 

MySQL로 앱 실행

Todo 앱은 MySQL 연결 설정을 지정하기 위해 몇 가지 환경 변수 설정을 지원합니다. 그들은:

  • MYSQL_HOST- 실행 중인 MySQL 서버의 호스트 이름
  • MYSQL_USER- 연결에 사용할 사용자 이름
  • MYSQL_PASSWORD- 연결에 사용할 비밀번호
  • MYSQL_DB- 연결되면 사용할 데이터베이스

 

[메모]

환경 변수를 사용하여 연결 설정을 지정하는 것은 일반적으로 개발용으로 허용되지만 프로덕션 환경에서 애플리케이션을 실행할 때는 권장되지 않습니다. Docker의 전 보안 책임자인 Diogo Monica가 환상적인 블로그 게시물을 작성했습니다.open_in_new 이유를 설명합니다.

보다 안전한 메커니즘은 컨테이너 오케스트레이션 프레임워크에서 제공하는 비밀 지원을 사용하는 것입니다. 대부분의 경우 이러한 비밀은 실행 중인 컨테이너에 파일로 탑재됩니다. 많은 앱(MySQL 이미지 및 todo 앱 포함)도 _FILE변수가 포함된 파일을 가리키는 접미사가 있는 env var를 지원하는 것을 볼 수 있습니다.

예를 들어, MYSQL_PASSWORD_FILEvar를 설정하면 앱이 참조 파일의 콘텐츠를 연결 비밀번호로 사용하게 됩니다. Docker는 이러한 환경 변수를 지원하기 위해 아무 작업도 수행하지 않습니다. 앱은 변수를 찾고 파일 콘텐츠를 가져오는 방법을 알아야 합니다.

 

위의 내용이 이해를 위해 아래의 내용을 참고.

 

위의 내용의 이해

: Node.js + Express 를 사용해서 프로젝트 개발을 할 때 MySQL 연결하는 코드를 환경변수를 사용하여 MySQL  데이터베이스에 대한 연결 정보를 설정하고 활용한다. 

 

dotenv 모듈을 설치하면, .env 파일을 사용할 수 있으며, .env 파일 내부에는 HOST, USER, PASSWORD, DATABASE 등 MySQL 접속에 필요한 정보들을 저장해두고, 로컬, 운영 환경에 맞게 .env 파일을 불러와서 MySQL 에 접속이 가능하다. 또한, .env 파일에는 MySQL 접속 정보 이외에도 다른 정보도 저장해두고 로컬, 운영 환경에 불리해서 사용도 가능하도록 개발이 가능하다.

 

.env 파일 에시

DB_HOST=localhost
DB_USER=myusername
DB_PASSWORD=mypassword
DB_DATABASE=mydatabase

 

Node.js 및 Express 예시 코드

// 필요한 패키지 가져오기
const express = require('express');
const mysql = require('mysql');
require('dotenv').config(); // dotenv 모듈을 사용하여 .env 파일 로드

// Express 애플리케이션 생성
const app = express();

// MySQL 데이터베이스 연결 정보
const connection = mysql.createConnection({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE
});

// MySQL 데이터베이스 연결
connection.connect((err) => {
  if (err) {
    console.error('Error connecting to MySQL database:', err);
    return;
  }
  console.log('Connected to MySQL database!');
});

// 라우트 예시
app.get('/', (req, res) => {
  res.send('Hello, World!');
});

// 서버 시작
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

 

위 코드에서, dotenv 모듈은 .env 파일을 로드하여 환경 변수에 접근할 수 있도록 합니다. 그리고 MySQL 연결 정보는 process.env를 사용하여 .env 파일에 정의된 환경 변수 값을 읽어옵니다. 이렇게 하면 소스 코드 내에 직접적으로 민감한 정보를 포함시키지 않고도, 환경 변수를 통해 설정 정보를 관리할 수 있습니다.

 

ENV 환경 변수에대해서 알았으니, 이제 메모에 적혀진 내용이 어떤 내용인지 아래에서 이해가 가능하다.

 

 

환경 변수(ENV 변수)를 사용하여 연결 설정이나 민감한 정보(예: 비밀번호, 키 등)를 설정하는 것은 개발 단계에서는 흔히 사용되지만, 프로덕션 환경에서는 안전하지 않을 수 있습니다. 여기에는 몇 가지 이유가 있습니다.

 

  1. 보안 위험: 환경 변수는 컨테이너 외부에서도 쉽게 볼 수 있습니다. 프로덕션 환경에서는 민감한 정보가 노출되면 보안 문제를 야기할 수 있습니다. 해커들은 환경 변수를 탈취하여 악용할 수 있습니다.
  2. 관리 어려움: 환경 변수를 통한 관리는 복잡해질 수 있습니다. 많은 환경 변수가 필요한 경우 이를 일일이 설정하고 관리하는 것은 번거로울 수 있습니다.
  3. Docker 이미지 노출: 환경 변수는 Docker 이미지에 하드코딩되어 이미지 자체에 포함됩니다. 따라서 이미지가 노출될 경우, 민감한 정보가 함께 노출될 수 있습니다.

컨테이너 오케스트레이션 프레임워크에서는 이러한 보안 이슈를 해결하기 위해 비밀(Secret)을 지원합니다. 이것은 민감한 정보를 컨테이너 내부로 전달하는 메커니즘이며, 일반적으로 컨테이너가 액세스할 수 있는 파일로 탑재됩니다.

예를 들어, 앱에서는 _FILE 접미사가 있는 환경 변수를 통해 파일에 있는 내용을 비밀 정보로 사용할 수 있습니다. MySQL 비밀번호의 경우, MYSQL_PASSWORD_FILE이라는 환경 변수를 설정하여 파일의 내용을 암호로 사용할 수 있습니다. Docker는 이러한 환경 변수를 읽거나 처리하지 않고, 애플리케이션이 이러한 환경 변수의 존재를 인식하고 파일에서 콘텐츠를 가져와 비밀 정보로 사용해야 합니다.

 

이를 통해 민감한 정보가 외부로부터 안전하게 보호되며, 컨테이너와 관련된 보안 문제를 최소화할 수 있습니다.

 

https://blog.diogomonica.com//2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/

 

Why you shouldn't use ENV variables for secret data

If your application requires a password, SSH private key, TLS Certificate, or any other kind of sensitive data, you shouldn't pass it alongside your configs.

blog.diogomonica.com

 

정리하자면,

 

환경 변수를 사용하여 로컬 개발 환경에서 민감한 정보(예: MySQL 연결 정보)를 안전하게 관리하는 것은 일반적으로 편리하고 효과적입니다. 로컬 개발에서는 .env 파일과 같은 방법으로 환경 변수를 설정하고, 소스 코드에서 이를 읽어와 MySQL과 같은 서비스에 연결하는 것이 보편적입니다.

 

그러나 프로덕션(운영) 환경에서는 환경 변수가 보안적인 측면에서 안전하지 않을 수 있습니다. 따라서 Docker와 같은 컨테이너 환경에서는 이러한 보안적인 문제를 해결하기 위해 MYSQL_PASSWORD_FILE와 같은 기능을 제공합니다.

MYSQL_PASSWORD_FILE와 비슷한 기능은 컨테이너 내부에 민감한 정보를 파일로 저장하고, 컨테이너에서 해당 파일을 읽어와 연결 정보로 사용하는 방식입니다. 이를 통해 환경 변수에 직접 민감한 정보를 저장하지 않고, 보안적인 측면을 강화할 수 있습니다.

 

즉, 프로덕션 환경에서는 Docker와 같은 환경에서 제공하는 비밀(Secrets) 또는 비밀 지원 기능을 사용하여 보안을 강화하고, 민감한 정보를 안전하게 관리할 수 있습니다. 이렇게 함으로써 민감한 정보가 외부로 노출되는 것을 방지하고, 보다 안전한 운영 환경을 구성할 수 있습니다.

 

 

이제 개발 준비가 완료된 컨테이너를 시작할 수 있다.

 

 
 
1. 이전 환경 변수를 각각 지정하고 컨테이너를 앱 네트워크에 연결합니다. getting-started-app이 명령을 실행할 때 해당 디렉터리에 있는지 확인하세요 .
 
 
[Window]
$ docker run -dp 127.0.0.1:3000:3000 `
  -w /app -v "$(pwd):/app" `
  --network todo-app `
  -e MYSQL_HOST=mysql `
  -e MYSQL_USER=root `
  -e MYSQL_PASSWORD=secret `
  -e MYSQL_DB=todos `
  node:18-alpine `
  sh -c "yarn install && yarn run dev"
 

2. 컨테이너( )에 대한 로그를 보면 docker logs -f <container-id>다음과 유사한 메시지가 표시되어야 합니다. 이는 해당 컨테이너가 mysql 데이터베이스를 사용하고 있음을 나타냅니다.
nodemon src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node src/index.js`
Connected to mysql db at host mysql
Listening on port 3000

 

3. 브라우저에서 앱을 열고 할 일 목록에 몇 가지 항목을 추가하세요.

4. mysql 데이터베이스에 연결하고 항목이 데이터베이스에 기록되고 있음을 증명합니다. 비밀번호는 입니다 secret.

$ docker exec -it <mysql-container-id> mysql -p todos

 

그리고 mysql 셀에서 아래의 명령어를 실행한다.

mysql> select * from todo_items;
+--------------------------------------+--------------------+-----------+
| id                                   | name               | completed |
+--------------------------------------+--------------------+-----------+
| c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! |         0 |
| 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome!        |         0 |
+--------------------------------------+--------------------+-----------+

 

테이블에 항목이 있으므로 테이블이 다르게 보일 것입니다. 하지만 거기에 저장된 것을 볼 수 있습니다.