서론
inception 과제는 도커를 활용해 여러 서비스를 컨테이너화하고, 직접 서버를 구축하는 프로젝트 프로젝트이다. 프로젝트를 올바르게 구현하면 웹 브라우저에서 login.42.fr 로 접속해서 워드프레스 서비스를 이용할 수 있다.
- 도커(Docker)를 사용하여 서비스별 컨테이너를 구성
- Docker Compose를 활용하여 컨테이너 간 연결 및 오케스트레이션
- 보안 및 성능을 고려한 서버 환경 구성
- WordPress, MariaDB, Nginx 등 웹 서비스 인프라 구성
Docker, Docker Compose?
도커와 도커 컴포즈에 대한 개념을 공부하고 과제를 시작하자.
도커란?
도커는 컨테이너(Container) 기술을 기반으로 애플리케이션을 쉽게 배포, 실행, 관리할 수 있도록 해주는 플랫폼이다.
이전에 쓴 글을 참고하자.
2025.02.09 - [대외활동/42서울] - [42서울] inception 도커의 개념 (가상환경, 볼륨, 동작 원리)
[42서울] inception 도커의 개념 (가상환경, 볼륨, 동작 원리)
도커는 애플리케이션을 격리된 컨테이너 환경에서 실행하여 개발과 운영의 효율성을 극대화하는 도구이다. 이번 글에서는 42서울 inception 과제 구현 및 평가 과정에서 배웠던 내용을 토대로 도
campus-coder.tistory.com
도커 컴포즈란?
Docker Compose는 여러 개의 도커 컨테이너를 하나의 설정 파일(docker-compose.yml)로 정의하고, 한 번의 명령어로 실행 및 관리할 수 있도록 도와주는 도구이다.
각각의 도커 컨테이너를 관리하는 것 이외에도 컨테이너 간의 설정도 도커 컴포즈로 편하게 설정할 수 있다.
- 네트워크:
- Docker 단독 사용: 컨테이너 간 네트워크를 수동으로 생성하고 연결해야 하며, 기본 브리지 네트워크는 제한적인 기능을 제공
- Docker Compose: YAML 파일에 선언적으로 네트워크 설정을 추가함으로써, 자동 네트워크 생성 및 내부 DNS를 통한 간편한 컨테이너 간 통신을 지원
- 볼륨:
- Docker 단독 사용: 볼륨을 생성하고 마운트 하는 작업을 수동으로 처리해야 하며, 여러 컨테이너 간의 데이터 공유나 재현성이 떨어짐
- Docker Compose: 볼륨 설정을 한 곳에 선언하여 자동 생성 및 관리할 수 있고, 환경 재현성 및 데이터 관리가 훨씬 간편
구현
mariadb, nginx, wordpress라는 각 서비스를 구분해서 디렉터리를 작성했다.
.env 파일은 과제를 제출하거나 git에 올릴 때 함께 올리지 않도록 주의해야 한다. (Key 등 민감한 정보)
docker-compose.yml
services:
mariadb:
container_name: mariadb
build: ./requirements/mariadb/.
volumes:
- mariadb:/var/lib/mysql
expose:
- "3306"
env_file:
- .env
networks:
- inception
restart: always
wordpress:
container_name: wordpress
build: ./requirements/wordpress/.
volumes:
- wordpress:/var/www/html
expose:
- "9000"
env_file:
- .env
networks:
- inception
restart: always
nginx:
container_name: nginx
build: ./requirements/nginx/.
volumes:
- wordpress:/var/www/html
ports:
- "443:443"
networks:
- inception
env_file:
- .env
restart: always
volumes:
wordpress:
name: wordpress
driver_opts:
device: /Users/johyeongeun/42/inception/srcs/wordpress
# device: /home/hyungcho/data/wordpress
o: bind
type: none
mariadb:
name: mariadb
driver_opts:
device: /Users/johyeongeun/42/inception/srcs/mariadb
# device: /home/hyungcho/data/mariadb
o: bind
type: none
networks:
inception:
name: inception
driver: bridge
이 docker-compose.yml 파일은 세 개의 컨테이너를 실행한다.
- mariadb (데이터베이스)
- wordpress (웹 애플리케이션)
- nginx (웹 서버)
또한, 각 컨테이너는 데이터를 유지하기 위해 볼륨을 사용하고, 서로 통신하기 위해 네트워크를 공유한다.
2. 서비스 정의
① MariaDB (데이터베이스)
mariadb:
container_name: mariadb
build: ./requirements/mariadb/.
volumes:
- mariadb:/var/lib/mysql
expose:
- "3306"
env_file:
- .env
networks:
- inception
restart: always
- container_name: mariadb → 컨테이너 이름을 mariadb로 지정
- build: ./requirements/mariadb/. → mariadb의 도커 이미지를 해당 경로에서 빌드
- volumes → MariaDB의 데이터(/var/lib/mysql)를 호스트 머신과 연결하여 데이터가 유지됨
- expose: "3306" → 내부적으로만 3306 포트를 노출(외부 접근은 불가능)
- .env 파일에서 환경 변수를 가져옴 (DB 이름, 사용자, 비밀번호 등)
- networks: inception → inception 네트워크를 사용하여 다른 컨테이너와 연결
- restart: always → 컨테이너가 죽어도 자동 재시작
② WordPress (웹 애플리케이션)
wordpress:
container_name: wordpress
build: ./requirements/wordpress/.
volumes:
- wordpress:/var/www/html
expose:
- "9000"
env_file:
- .env
networks:
- inception
restart: always
- build: ./requirements/wordpress/. → WordPress 컨테이너 빌드
- volumes → WordPress 데이터를 /var/www/html과 연결해 유지
- expose: "9000" → 내부적으로만 9000 포트를 노출
- .env 파일에서 환경 변수 로드 (DB 정보 등)
- restart: always → 자동 재시작
③ Nginx (웹 서버)
nginx:
container_name: nginx
build: ./requirements/nginx/.
volumes:
- wordpress:/var/www/html
ports:
- "443:443"
networks:
- inception
env_file:
- .env
restart: always
- build: ./requirements/nginx/. → Nginx 컨테이너 빌드
- volumes → WordPress 파일을 /var/www/html과 연결
- ports: "443:443" → 호스트(외부)와 컨테이너(내부) 간 443 포트 연결
- .env 파일에서 환경 변수 로드
- restart: always → 자동 재시작
3. 볼륨 설정 (데이터 유지)
volumes:
wordpress:
name: wordpress
driver_opts:
device: /home/hyungcho/data/wordpress
o: bind
type: none
mariadb:
name: mariadb
driver_opts:
device: /home/hyungcho/data/mariadb
o: bind
type: none
- WordPress와 MariaDB 데이터를 호스트 디렉터리에 저장하여 컨테이너가 종료되어도 데이터 유지
- device: ... → 호스트의 특정 경로를 컨테이너 내부에 마운트
4. 네트워크 설정 (컨테이너 간 통신)
networks:
inception:
name: inception
driver: bridge
- bridge 네트워크를 사용하여 MariaDB, WordPress, Nginx가 같은 네트워크에서 통신 가능
5. 실행 방법
docker-compose up -d --build
- -d → 백그라운드 실행.
- --build → 필요하면 컨테이너 이미지를 다시 빌드
mariadb
Dockerfile
FROM debian:bullseye
RUN apt-get update -y && \
apt-get upgrade -y && \
apt-get install mariadb-server -y
COPY ./tools/my.cnf /etc/mysql/mariadb.conf.d/
COPY ./tools/script.sh /
CMD ["/script.sh"]
mariadb 패키지를 설치한다.
호스트에서 도커 컨테이너로 파일을 복사하고 db 설정과 실행을 위해 작성한 스크립트를 CMD로 실행시킨다.
Script.sh
#!/bin/bash
service mariadb start
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "CREATE DATABASE IF NOT EXISTS ${MYSQL_DATABASE_NAME};"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "CREATE USER IF NOT EXISTS '${MYSQL_USER}'@'%' IDENTIFIED BY '${MYSQL_PASSWORD}';"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "GRANT ALL PRIVILEGES ON ${MYSQL_DATABASE_NAME}.* TO '${MYSQL_USER}'@'%';"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}';"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "FLUSH PRIVILEGES;"
mysqladmin -u root -p"${MYSQL_ROOT_PASSWORD}" shutdown
mysqld_safe
데이터베이스를 생성, 유저 및 비밀번호 생성, 유저에게 데이터 베이스에 대한 권한 부여, root 비밀번호 수정, Flush, 종료한다.
이후 마리아디비를 실행시켜 준다.
my.cnf
[mysqld]
user = mysql
pid-file = /run/mysqld/mysqld.pid
socket = /run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
bind-address = 0.0.0.0
# 기본 문자셋 설정
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
# 로그 파일 설정
log_error=/var/log/mysql/error.log
# InnoDB 기본 설정
innodb_buffer_pool_size=256M
/etc/mysql/mariadb.conf.d/
이 경로에 my.cnf라는 파일을 정의하면 mariadb 설정 파일로 적용된다.
wordpress
워드프레스는 개인이 소유하고 배포할 수 있는 블로그이다.
Dockerfile
FROM debian:bullseye
RUN apt-get update -y && \
apt-get upgrade -y && \
apt-get -y install \
php7.3 \
php-fpm \
php-cli \
wget \
curl \
php-mysql \
php-mbstring \
php-xml \
sendmail
COPY ./tools/wp-config.php /
COPY ./tools/script.sh /
CMD ["/script.sh"]
워드프레스와 관련된 패키지를 설치한다.
Script.sh
#!/bin/bash
sleep 3
cp /wp-config.php /var/www/html/
cd /var/www/html/
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
mv wp-cli.phar /usr/local/bin/wp
wp core download --allow-root
wp core install --url=${DOMAIN_NAME}/ --title=${WP_TITLE} --admin_user=${WP_ADMIN_USER} --admin_password=${WP_ADMIN_PWD} --admin_email=${WP_ADMIN_EMAIL} --skip-email --allow-root
wp user create ${WP_USER} ${WP_EMAIL} --role=author --user_pass=${WP_PWD} --allow-root
sed -i 's/listen = \/run\/php\/php7.4-fpm.sock/listen = 0.0.0.0:9000/g' /etc/php/7.4/fpm/pool.d/www.conf
sed -i 's/;clear_env = no/clear_env = no/g' /etc/php/7.4/fpm/pool.d/www.conf
mkdir -p /run/php
/usr/sbin/php-fpm7.4 -F
워드프레스 config 파일을 docker-compose 파일에서 정의했던 볼륨으로 복사한다. 도커 파일이 아니라 script.sh 파일에서 config 파일을 복사해야 올바르게 작동한다. Dockerfile의 COPY -> 볼륨 마운트 -> script.sh 순서로 동작하기 때문에 도커파일의 COPY로 config 파일을 복사한다면 볼륨 마운트 과정에서 config 파일이 날아가게(디렉터리가 덮어씌워지게) 된다.
워드프레스 cli를 설치한다. cli는 명령어를 통해 워드프레스를 설정할 수 있도록 하는 도구이다.
워드프레스에 어드민 유저와 일반 유저를 생성한다.
워드프레스의 설정을 하고 0.0.0.0:9000으로 통신할 수 있도록 php7.4-fpm.sock 파일을 수정해 준다.
fpm 기본 설정 때문에 환경변수가 날아가지 않도록 clear_env = no로 수정해 준다.
마지막으로 워드프레스를 포그라운드로 실행시켜 준다.
wp-config.php
// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', getenv('MYSQL_DATABASE_NAME') );
/** Database username */
define( 'DB_USER', getenv('MYSQL_USER') );
/** Database password */
define( 'DB_PASSWORD', getenv('MYSQL_PASSWORD') );
/** Database hostname */
define( 'DB_HOST', 'mariadb' );
/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8' );
/** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );
/**#@+
* Authentication unique keys and salts.
*
* Change these to different unique phrases! You can generate these using
* the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
*
* You can change these at any point in time to invalidate all existing cookies.
* This will force all users to have to log in again.
*
* @since 2.6.0
*/
define('AUTH_KEY', getenv('WP_AUTH_KEY'));
define('SECURE_AUTH_KEY', getenv('WP_SECURE_AUTH_KEY'));
define('LOGGED_IN_KEY', getenv('WP_LOGGED_IN_KEY'));
define('NONCE_KEY', getenv('WP_NONCE_KEY'));
define('AUTH_SALT', getenv('WP_AUTH_SALT'));
define('SECURE_AUTH_SALT', getenv('WP_SECURE_AUTH_SALT'));
define('LOGGED_IN_SALT', getenv('WP_LOGGED_IN_SALT'));
define('NONCE_SALT', getenv('WP_NONCE_SALT'));
docker-compose 파일에서 .env 파일을 환경변수로 설정했으니 wp-config 파일에서는 getenv()를 통해 환경변수를 가져온다.
Databases 세팅에서 환경변수 값은 내가 임의로 정하면 되고, auth unique key 부분은 주석을 잘 읽고 사이트에 접속해서 키를 생성한 후 .env 파일에 세팅해 주면 된다.
nginx
nginx는 가볍고 빠른 웹 서버이자 리버스 프록시, 로드 밸런서 등 다양한 역할을 한다.
Dockerfile
FROM debian:bullseye
RUN apt-get update -y && \
apt-get upgrade -y && \
apt-get -y install nginx openssl
COPY ./tools/script.sh /script.sh
RUN chmod +x /script.sh
CMD ["/script.sh"]
nginx와 onepssl을 설치한다.
OpenSSL은 데이터 암호화, 복호화, 보안 통신(SSL/TLS)을 위한 오픈 소스 라이브러리이다. https 적용을 위해 설치한다.
Script.sh
#!/bin/bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.pem -out $CERTS_ -subj "/C=KR/L=SEOUL/O=42/OU=student/CN=hyungcho.42.fr"
echo "
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name www.$DOMAIN_NAME $DOMAIN_NAME;
ssl_certificate $CERTS_;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.pem;" > /etc/nginx/sites-available/default
echo '
ssl_protocols TLSv1.3;
index index.php index.html index.htm;
root /var/www/html;
location ~ [^/]\.php(/|$) {
try_files $uri =404;
fastcgi_pass wordpress:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
} ' >> /etc/nginx/sites-available/default
nginx -g "daemon off;"
- OpenSSL로 SSL 인증서 생성:
- openssl req -x509 -nodes -days 365 -newkey rsa:2048
- x509: 자체 서명 인증서 생성
- nodes: 암호 없이 비밀키 생성
- days 365: 유효기간 1년
- rsa:2048: 2048비트 RSA 키 사용
- 인증서: $CERTS_
- 키 파일: /etc/ssl/private/nginx-selfsigned.pem
- openssl req -x509 -nodes -days 365 -newkey rsa:2048
- Nginx 설정 파일 작성:
- 443 포트(TLS)로 서비스
- 서버 이름: $DOMAIN_NAME
- SSL 프로토콜: TLSv1.3
- PHP 요청 처리: fastcgi_pass wordpress:9000
- Nginx 실행:
- nginx -g "daemon off;" → 포그라운드 모드 실행 (Docker 컨테이너 환경)
테스트 및 결과 확인
몇몇 테스트 화면을 확인해 보자.
volume가 device의 원하는 위치에 마운트 된 것을 확인할 수 있다.
테스트용으로 개인 맥북에서 돌린 거라 위에 작성했던 /home/hyungcho/42와는 경로가 다르다.
inception 네트워크를 제외한 나머지 3개는 기본 네트워크이다.
데이터베이스가 잘 생성되었다.
유저도 잘 생성되었다.
마지막으로 워드프레스 페이지에 잘 접속되는 것을 확인할 수 있었다.
위쪽에 주의 요함은 내가 자체로 발급한 https 인증서이기에 보안 경고를 띄우는 것이다. 실제 배포에서는 도메인 판매 사이트에서 도메인을 구입하고 인증서 발급받아 사용하면 된다.
도커와 PID 1 (feat. 동료 평가)
과제를 동료 평가받는 중에 새로운 사실을 알게 되었다. 내가 작성한 도커 파일에서는 cmd를 통해 스크립트를 실행시켰는데, 컨테이너에 접속해 ps aux 명령으로 상태를 확인해 보니 pid1로 script.sh가 돌아가고 있었다. 서비스가 잘 동작했기에 왜 문제인지 모르고 있었지만, 사실 pid1은 부모 프로세스며 자식 프로세스를 관리하기 위해 자식 프로세스에서 보내는 시그널 처리를 해줘야 하는데 내가 작성한 스크립트 파일에서는 시그널 처리를 해주지 못한다는 것이 문제였다. 자식 프로세스에서 fatal error가 발생한다 해도 정상적으로 이 오류를 처리해주지 못하는 상황이었다.
해결 방법은 두 가지가 존재한다.
첫 번째 방법, 내가 작성한 파일을 pid1로 사용하고 시그널 처리를 해주기이다. 복잡한 방법이므로 두 번째 방법을 사용하자.
두 번째 방법, docker의 --init 옵션을 사용해서 도커에서 제공하는 init 프로세스를 pid1로 사용하자. 도커에서 제공하는 프로세스는 pid1에 필요한 처리가 잘 되어있다. 도커 컴포즈 파일에서 init: true도 가능하다.
# docker-compose
services:
app:
image: my-app-image
container_name: my-app
init: true # Init 옵션 추가
나는 두 번째 방법을 사용해서 이 문제를 해결했고, 컨테이너에 접속해서 ps aux 명령으로 확인해 보니 도커의 init 프로세스가 pid1로 잘 작동하고 있는 것을 확인할 수 있었다.
'대외활동 > 42서울' 카테고리의 다른 글
[42서울] inception 도커의 개념 (가상환경, 볼륨, 동작 원리) (1) | 2025.02.09 |
---|---|
[42서울] cpp08 구현 (1) | 2024.11.27 |
[42서울] cpp07 구현 (0) | 2024.11.26 |
[42서울] Netpractice LEVEL 8 문제 풀이 (1) | 2024.11.11 |
[42서울] minitalk 구현(Makefile 포함) (0) | 2024.08.22 |