계정 정보가 있는 보통의 웹 어플리케이션은 DBMS를 필수적으로 사용하게 된다.
신규 기능이나, 제품 구조가 변경되어야 하는 개발 요구사항이 있다고 가정해보자.
이 때 제품의 버전이나 브랜치 단위로 DB 테이블 스키마가 변경될 수 있으며,
서버 설치 시 필수적인 초기 데이터 또한 추가/수정/삭제될 수 있다.
CI/CD 환경을 구성할 때 Server, Front 소스만 통합하여 배포한다면
변경된 테이블 스키마, 추가/수정/삭제된 데이터는 DB에 반영되지 않은 상태이므로
무결한 배포 환경을 구성했다고 할 수 없을 것이다.
배포 때마다 개발자가 매 번 변경된 DDL을 실행해주고, 누락된 데이터를 수기로 넣어준다면
해당 DB를 사용한 배포는 무결한 상태라고 말할 수 있을까?
이러한 고민을 해결하기 위해 Jenkins에서는 아래와 같은 작업을 진행하게 된다.
이 과정 중 “DBMS 인스턴스를 재구성하고 설치 스크립트를 수행”하기 위해
빠른 시작/종료, 재구성에 용이한 Docker 컨테이너로 DBMS 인스턴스를 관리해야 한다.
Docker Compose는 Docker 컨테이너를 사전에 yaml 파일로 정의하여 실행하기 위한 도구이다.
추가적인 내용과 설치/사용 방법은 공식 문서 또는 오픈소스를 참조하도록 하자.
우선 컨테이너 정의 파일인 docker-compose-db.yml
파일을 생성하고 아래 내용을 추가한다.
내 시스템의 지원 DBMS 세 가지 Oracle, Postgresql, Tibero가 정의되어 있다.
# Docker 버전
version: "3"
# Docker 컨테이너 서비스 정의
services:
# Oracle
db-oracle:
# 이미지 명:태그 대신 Dockerfile 사용
# Dockerfile - Docker 이미지를 빌드하는 데 사용
build:
# Dockerfile의 경로
context: ../db/oracle/
# Dockerfile의 이름
dockerfile: Dockerfile
container_name: oracleHA-database
restart: always
# 컨테이너 환경변수를 파일로 정의 (DB스키마 Username, PW 정의)
env_file:
- ../db/oracle/oracle.env
# 볼륨 매핑 (어플리케이션 설치 스크립트 경로)
volumes:
- ../db/oracle/script:/docker-entrypoint-initdb.d
# 호스트 서버:컨테이너 포트 매핑
ports:
- "11521:1521"
# Postgresql
db-postgres:
# 이미지:태그
image: postgres:13-alpine
container_name: postgres13HA-database
restart: always
# 컨테이너 환경변수
environment:
- POSTGRES_PASSWORD=1234
# 볼륨 매핑 (어플리케이션 설치 스크립트 경로)
volumes:
- ../db/postgres/script:/docker-entrypoint-initdb.d
# 호스트 서버:컨테이너 포트 매핑
ports:
- "15432:5432"
# Tibero
db-tibero:
# 이미지 명:태그 대신 Dockerfile 사용
build:
context: ../db/tibero/tibero-docker/
dockerfile: Dockerfile
container_name: tiberoHA-database
# OS 호스트네임 (티베로 라이센스 파일용)
hostname: docker_tibero
stdin_open: true
# 컨테이너 커널 매개변수
# Docker 컨테이너는 호스트 OS의 커널 매개변수를 참조하여 로딩되므로
# 경우에 따라 수정 필요
sysctls:
- kernel.sem=10000 32000 10000 10000
# TCP 소켓 개수 설정
- net.ipv4.ip_local_port_range=1024 65500
# 환경변수
environment:
- LANG=en_US.UTF-8
- TB_MAX_SESSION_COUNT=100
- TB_MEMORY_TARGET=4G
- TB_TOTAL_SHM_SIZE=2G
# specify all below for triggering tbimport from '/opt/tibero/dump'
- TB_IMPORT_ENABLE=true # default `false`
- TB_IMPORT_SID=TIBERO
- TB_IMPORT_PORT=8629
- TB_IMPORT_USERNAME=XEDRM5
- TB_IMPORT_PASSWORD=1234
- TB_IMPORT_SCRIPT=y
- TB_IMPORT_IGNORE=y
- TB_IMPORT_ROWS=y
- TB_IMPORT_CONSTRAINT=y
- TB_IMPORT_INDEX=y
- TB_IMPORT_TRIGGER=y
- TB_IMPORT_SYNONYM=n
- TB_IMPORT_SEQUENCE=y
- TB_IMPORT_USER=XEDRM5
- TB_NLS_LANG=UTF8
# 볼륨 동기화
volumes:
- ../db/tibero/tibero/init/:/opt/tibero/init
- ../db/tibero/tibero/license:/opt/tibero/license
- ../db/tibero/tibero/dump:/opt/tibero/dump
# 볼륨 매핑 (어플리케이션 설치 스크립트 경로)
- ../db/tibero/script:/scripts
# 호스트 서버:컨테이너 포트 매핑
ports:
- "18629:8629"
docker-compose-db.yml
파일로 정의한 Docker 컨테이너를 실행해주는 쉘 파일을 생성한다.
docker-onlydb.sh
파일을 생성하고 아래 내용을 입력한다.
해당 쉘 파일은 실행 시 start, stop, restart 매개변수를 받아 컨테이너를 실행/중지/재시작할 수 있도록 한다.
이제 DBMS 별 상세 설정에 대해 살펴보자
#!/bin/bash
case "$1" in
'start') # Start docker oracle & postgres & tibero
echo "Starting Docker Oracle & Postgres & Tibero Service...: "
#docker compose 실행명령
docker-compose -f ./docker-compose-db.yml up --build -d
RVAL=$?
echo $RVAL
exit $RVAL
;;
'stop') # Stop docker oracle & postgres & tibero
echo "Stop Docker Oracle & Postgres & Tibero Service...: "
docker-compose -f ./docker-compose-db.yml down
exit $RVAL
;;
'restart') # Restart docker oracle & postgres & tibero
echo "Restarting Docker Oracle & Postgres & Tibero Service...: "
docker-compose -f ./docker-compose-db.yml down
docker-compose -f ./docker-compose-db.yml up --build -d
RVAL=$?
echo $RVAL
exit $RVAL
;;
esac
docker-compose-db.yml
파일에 사전 정의한 DBMS 별 컨테이너를 시작하기 위해
docker-onlydb.sh
아래 명령어로 쉘 파일을 실행한다.
~$ ./docker-onlydb.sh start
docker-compose-db.yml
파일에서 정의한 Oracle 서비스 컨테이터의 환경변수 파일 oracle.env
내용은 아래와 같다.
# 오라클 유저 패스워드를 변경하기 위한 환경변수
JDBC_USERNAME=XEDRM5
JDBC_PASSWORD=1234
JDBC_USERNAME1=XEDRM6
JDBC_PASSWORD1=1234
docker-compose-db.yml
파일에 정의한 Oracle 컨테이너 Dockerfile의 내용은 아래와 같다.
FROM 구문에서 어떤 Docker 이미지를 사용할지 정의한다.
Oracle Database는 Docker 공식 이미지가 없으므로 다른 사용자의 이미지를 활용했다.
구문 별 상세 내용은 아래 주석을 참고하면 된다.
# Docker 컨테이너 이미지
FROM jaspeen/oracle-xe-11g
# 작성자 데이터를 기입 (MAINTAINER : deprecated)
LABEL name="stdrunner"
LABEL email="kjb119912@gmail.com"
LABEL description="Oracle Database xe-11g"
# ADD : 호스트 서버의 파일을 컨테이너 특정 경로로 추가
ADD entrypoint.sh /entrypoint.sh
ADD setPassword.sh /setPassword.sh
ADD setUserPassword.sh /setUserPassword.sh
# RUN : Linux에선 "/bin/sh -c" 로 쉘 실행
# 쉘 파일 실행 권한 추가
RUN chmod -R +x /entrypoint.sh
RUN chmod -R +x /setPassword.sh
RUN chmod -R +x /setUserPassword.sh
# /db 폴더 추가
RUN mkdir /db
RUN chmod -R +x /db
# 컨테이너 실행 시 수행해야할 쉘 작업 실행
ENTRYPOINT ["/entrypoint.sh"]
Dockerfile 내에서 컨테이너 기동 작업으로 수행하는 entrypoint.sh
파일의 주요 내용을 살펴보자.
#!/bin/bash
# Oracle 설치 폴더 하위 소유자 변경 (소유자 이슈 예방)
chown -R oracle:dba /u01/app/oracle
# 기존 product 폴더 제거 및 심볼릭 링크로 대체
rm -f /u01/app/oracle/product
ln -s /u01/app/oracle-product /u01/app/oracle/product
# CHARSET 환경변수 설정
export CHARACTER_SET="AL32UTF8"
echo "CHARACTER_SET=AL32UTF8"
if [ -z "$CHARACTER_SET" ]; then
if [ "${USE_UTF8_IF_CHARSET_EMPTY}" == "true" ]; then
export CHARACTER_SET="AL32UTF8"
#export CHARACTER_SET="KO16MSWIN949"
#echo "CHARACTER_SET=KO16MSWIN949"
fi
fi
if [ -n "$CHARACTER_SET" ]; then
export CHARSET_MOD="NLS_LANG=.$CHARACTER_SET"
export CHARSET_INIT="-characterSet $CHARACTER_SET"
echo "CHARSET_INIT=-characterSet $CHARACTER_SET"
fi
# hostname 갱신
sed -i -E "s/HOST = [^)]+/HOST = $HOSTNAME/g" /u01/app/oracle/product/11.2.0/xe/network/admin/listener.ora
# port 갱신
sed -i -E "s/PORT = [^)]+/PORT = 1521/g" /u01/app/oracle/product/11.2.0/xe/network/admin/listener.ora
# ORACLE 환경변수 추가 및 반영
echo "export ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe" > /etc/profile.d/oracle-xe.sh
echo "export PATH=\$ORACLE_HOME/bin:\$PATH" >> /etc/profile.d/oracle-xe.sh
echo "export ORACLE_SID=XE" >> /etc/profile.d/oracle-xe.sh
. /etc/profile
case "$1" in
'')
# 오라클 초기화 Config 설정
#Check for mounted database files
if [ "$(ls -A /u01/app/oracle/oradata)" ]; then
# 오라클 자동실행 OFF로 설정 : /etc/oratab
echo "Custom found files in /u01/app/oracle/oradata Using them instead of initial database"
echo "XE:$ORACLE_HOME:N" >> /etc/oratab
chown oracle:dba /etc/oratab
chown 664 /etc/oratab
# 오라클 초기화 Config 설정 : /etc/default/oracle-xe
printf "ORACLE_DBENABLED=false\nLISTENER_PORT=1521\nHTTP_PORT=8080\nCONFIGURE_RUN=true\n" > /etc/default/oracle-xe
rm -rf /u01/app/oracle-product/11.2.0/xe/dbs
ln -s /u01/app/oracle/dbs /u01/app/oracle-product/11.2.0/xe/dbs
else
echo "Custom Database not initialized. Initializing database."
export IMPORT_FROM_VOLUME=true
printf "Setting up:\nprocesses=$processes\nsessions=$sessions\ntransactions=$transactions\n"
echo "If you want to use different parameters set processes, sessions, transactions env variables and consider this formula:"
printf "processes=x\nsessions=x*1.1+5\ntransactions=sessions*1.1\n"
mv /u01/app/oracle-product/11.2.0/xe/dbs /u01/app/oracle/dbs
ln -s /u01/app/oracle/dbs /u01/app/oracle-product/11.2.0/xe/dbs
#Setting up processes, sessions, transactions.
echo "Setting up processes, sessions, transactions."
sed -i -E "s/processes=[^)]+/processes=$processes/g" /u01/app/oracle/product/11.2.0/xe/config/scripts/init.ora
sed -i -E "s/processes=[^)]+/processes=$processes/g" /u01/app/oracle/product/11.2.0/xe/config/scripts/initXETemp.ora
sed -i -E "s/sessions=[^)]+/sessions=$sessions/g" /u01/app/oracle/product/11.2.0/xe/config/scripts/init.ora
sed -i -E "s/sessions=[^)]+/sessions=$sessions/g" /u01/app/oracle/product/11.2.0/xe/config/scripts/initXETemp.ora
sed -i -E "s/transactions=[^)]+/transactions=$transactions/g" /u01/app/oracle/product/11.2.0/xe/config/scripts/init.ora
sed -i -E "s/transactions=[^)]+/transactions=$transactions/g" /u01/app/oracle/product/11.2.0/xe/config/scripts/initXETemp.ora
printf 8080\\n1521\\noracle\\noracle\\ny\\n | /etc/init.d/oracle-xe configure
echo "Database initialized. Please visit http://#containeer:8080/apex to proceed with configuration"
fi
# 오라클 인스턴스 기동
/etc/init.d/oracle-xe start
echo "oracle password settings ================================================================== $syspassword"
# setPassword.sh : Dockerfile에서 정의한 호스트 서버에서 컨테이너로 옯긴 파일
# Oracle User Password 변경 쉘
/setPassword.sh $JDBC_PASSWORD
# Oracle Database 초기 실행 여부 판단
# 공유 볼륨에서 설치 스크립트 수행
if [ $IMPORT_FROM_VOLUME ]; then
echo "Starting import from '/docker-entrypoint-initdb.d':"
# /docker-entrypoint-initdb.d/ 경로 하위 파일 LOOP
# sh, sql, dmp 파일 실행, 이외의 파일은 미실행
# impdb 명령이 없어 불가
for f in $(ls /docker-entrypoint-initdb.d/*); do
echo "found file $f"
case "$f" in
*.sh) echo "[IMPORT] $0: running $f"; . "$f" ;;
# sqlplus slient 모드로 sql파일 실행
*.sql) echo "[IMPORT] $0: running $f"; echo "exit" | su oracle -c "$CHARSET_MOD $ORACLE_HOME/bin/sqlplus -S / as sysdba @$f"; echo ;;
*.dmp) echo "[IMPORT] $0: running $f"; "$CHARSET_MOD $ORACLE_HOME/bin/impdp system/$JDBC_PASSWORD dumpfile=$f"; echo ;;
*) echo "[IMPORT] $0: ignoring $f" ;;
esac
echo
done
echo "Import finished"
echo
else
echo "[IMPORT] Not a first start, SKIPPING Import from Volume '/docker-entrypoint-initdb.d'"
echo "[IMPORT] If you want to enable import at any state - add 'IMPORT_FROM_VOLUME=true' variable"
echo
fi
# App DB 유저 계정 변경
# setUserPassword.sh : Dockerfile에서 정의한 호스트 서버에서 컨테이너로 옯긴 파일
echo "oracle user password settings ================================================================== $JDBC_USERNAME"
echo "oracle user password settings ================================================================== $JDBC_PASSWORD"
echo "oracle user password settings ================================================================== $JDBC_USERNAME1"
echo "oracle user password settings ================================================================== $JDBC_PASSWORD1"
/setUserPassword.sh $JDBC_USERNAME '$JDBC_PASSWORD'
/setUserPassword.sh $JDBC_USERNAME1 '$JDBC_PASSWORD1'
echo "Database ready to use. Enjoy! ;)"
##
## Workaround for graceful shutdown. ....ing oracle... ‿( ́ ̵ _-`)‿
##
## trap : 쉘에서 발행하는 시그널을 처리하는 로직 수행
## INT : Del 키를 눌렀을 때 발생하는 시그널
## TERM : 프로세스 종료 시그널
while [ "$END" == '' ]; do
sleep 1
trap "/etc/init.d/oracle-xe stop && END=1" INT TERM
done
;;
*)
echo "Database is not configured. Please run /etc/init.d/oracle-xe configure if needed."
$1
;;
esac
entrypoint.sh
에서 사용하기 위해 컨테이너로 이동한 파일을 확인해보자
첫 번쨰 인자로 받은 값을 Oracle SYS
, SYSTEM
계정의 패스워드를 변경하는 쉘 스크립트이다.
Dockerfile
에서 Oracle 컨테이너 내부로 복사하고 entrypoint.sh
내부에서 실행됨으로써 Oracle 인스턴스 초기화와 함께
패스워드가 초기화된다.
#!/bin/bash
ORACLE_PWD=$1
su -p oracle -c "$ORACLE_HOME/bin/sqlplus -S / as sysdba << EOF
ALTER USER SYS IDENTIFIED BY \"$ORACLE_PWD\";
ALTER USER SYSTEM IDENTIFIED BY \"$ORACLE_PWD\";
exit;
EOF"
인자 두 개(ID, PW)를 받아 어플리케이션 DB 스키마의 패스워드를 변경하는 쉘 스크립트이다.
Dockerfile
에서 Oracle 컨테이너 내부로 복사하고 entrypoint.sh
내부에서 실행됨으로써 Oracle 인스턴스 초기화와 함께
사전 생성된 어플리케이션 스키마의 패스워드가 변경된다.
#!/bin/bash
ORACLE_ID=$1
ORACLE_PWD=$2
su -p oracle -c "$ORACLE_HOME/bin/sqlplus -S / as sysdba << EOF
SELECT $ORACLE_PWD FROM DUAL;
ALTER USER $ORACLE_ID IDENTIFIED BY \"$ORACLE_PWD\";
exit;
EOF"
echo "ALTER USER $ORACLE_ID IDENTIFIED BY $ORACLE_PWD"