주녁, DevNote
article thumbnail
Published 2023. 1. 19. 00:19
MSA Dockerizing (3) - 발전시키기 DevOps

1. 목표

이전 편에서 작성한 내용에 CI/CD를 추가해보자

  • 소스 위치, 버전 정보를 저장소에서 가져오자
  • release 브랜치에 push가 발생하면 docker 이미지로 배포할 수 있도록 하자
    • 배포 버전은 커밋ID를 넣을 수 있도록 하자
  • 배포하는 이미지의 크기를 줄이자

2. 여정

2.1. 컨테이너와 프로젝트 간 의존성 제거

  1. docker 이미지를 Registry에 등록

    각 Dockerfile마다 Container Registry에 등록하여 이미지를 pull할 수 있도록 등록

    <code />
    # Gitlab의 Private image hub에 저장할 수 있도록 로그인 docker login registry.gitlab.com # Image naming convention에 따라 아래와 같이 Build 및 Push docker build -t <registry URL>/<namespace>/<project>/<image>:<tag> . docker push <registry URL>/<namespace>/<project>/<image>:<tag>
  2. 기존 Docker 실행관련 정보 분리

    • Container 정보를 가지고 있는 Docker Compose용 Repository를 새로 생성함.

      → 서비스는 서비스에만 집중하기 위함 : MSA의 특징인 인터페이스 통신을 살려보자

      • 서비스와 DB : DB 관련 Container 정보를 몰라도 됨

        • DB 설정은 환경변수를 활용

          <code />
          # database.properties schema.name=${DB_SCHEMA_NAME} spring.datasource.username=${DB_USER} spring.datasource.password=${DB_PASSWORD} spring.datasource.url=jdbc:${DB_KIND}:${DB_DELIMITER}${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicode=true&characterEncoding=utf8&currentSchema=${DB_SCHEMA_NAME} spring.datasource.driver-class-name=${DB_DRIVER_CLASS_NAME} spring.jpa.hibernate.dialect=${DB_DIALECT} # dbType=${DB_TYPE} # JPA spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.format_sql=false spring.jpa.show-sql=false
      • 서비스와 서비스 : 다른 서비스 Container 정보를 몰라도 됨

    • 다음과 같은 구조를 가지게 됨

      • DB 설정파일

        • /conf/DB설정할 SQL파일(*.sql) → 각자 필요에 맞게 작성

        • /conf/initdb.sh

          • Postgres

            <code />
            #!/bin/bash # initdb.sh for PostgreSql # Create a database and initialize it export SQL_FILE_PATH="$CONFIG_PATH"/"$DB_TYPE".sql sudo sed -i "s/{SERVICE_B_SCHEMA_NAME}/$DB_SCHEMA_NAME/g" "$SQL_FILE_PATH" su - postgres -c "psql -U $DB_USER -c \"CREATE DATABASE $DB_NAME\"" su - postgres -c "psql -U $DB_USER -d $DB_NAME -c \"CREATE SCHEMA $DB_SCHEMA_NAME\"" su - postgres -c "psql -U $DB_USER -d $DB_NAME -a -f $SQL_FILE_PATH"
          • Oracle

            <code />
            #!/bin/bash # initdb.sh for Oracle # Create a database and initialize it export SQL_FILE_PATH="$CONFIG_PATH"/"$DB_TYPE".sql su - oracle -c "echo \"create user $DB_USER identified by $DB_PASSWORD;\" | sqlplus / as sysdba" su - oracle -c "echo \"grant dba to $DB_USER;\" | sqlplus / as sysdba" su - oracle -c "sqlplus \"$DB_USER/$DB_PASSWORD@$DB_NAME\" < \"$SQL_FILE_PATH\""
        • ./DB Dockerfile

          <code />
          # Database FROM postgres:14 ENV APPNAME SERVICE_B ENV WORKDIR /usr/src/app WORKDIR $WORKDIR ENV CONFIG_PATH /usr/src/conf COPY ./conf $CONFIG_PATH # 타임존 설정 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ && echo $TZ > /etc/timezone # update 및 패키지 설치, init 명령어 등록 RUN apt update \ && apt install -y sudo sed \ && cat $CONFIG_PATH/initdb.sh >> /bin/initdb \ && chmod u+x /bin/initdb
      • 환경변수 설정파일(.env)

        • Postgres

          • .env 파일(Docker Compose용)

            <code />
            # .env for PostgreSql WORKDIR=/usr/src/app # DB Configuration DB_HOST=db_instance # 외부 DB와 연결하고 싶을 때 DB IP 입력 DB_ENV_FILE=.env.db DB_PORT_IN=5432 DB_PORT_OUT=5432 DB_IMAGE=registry.gitlab.com/... DB_IMAGE_VERSION=postgres-7.1.0 DB_DEFAULT_PATH=/var/lib/postgresql/data # SERVICE_B Configuration SERVICE_B_IMAGE=registry.gitlab.com/... SERVICE_B_IMAGE_VERSION=latest SERVICE_B_NAME=SERVICE_B1 # SERVICE_D Configuration SERVICE_D_IMAGE=registry.gitlab.com/... SERVICE_D_IMAGE_VERSION=latest SERVICE_D_NAME=kit1 # Ports SERVICE_B_PORT_IN=8080 SERVICE_B_PORT_OUT=8080 SERVICE_D_PORT_IN=8888 SERVICE_D_PORT_OUT=8888 SERVICE_D_CACHE_PORT_IN=8900 SERVICE_D_CACHE_PORT_OUT=8900
          • .env.db 파일(DB용)

            <code />
            # DB Configuration DB_USER=... DB_PASSWORD=... # Postgresql POSTGRES_PASSWORD=${DB_PASSWORD} DB_NAME="db" DB_SCHEMA_NAME="schema" DB_KIND="postgresql" DB_DELIMITER="//" DB_DIALECT="org.hibernate.dialect.PostgreSQL10Dialect" DB_DRIVER_CLASS_NAME="org.postgresql.Driver" DB_TYPE="postgresql_table" TZ="Asia/Seoul"
        • Oracle

          • .env 파일(Docker Compose용)

            <code />
            # .env for Oracle WORKDIR=/usr/src/app # DB Configuration DB_HOST=db_instance # 외부 DB와 연결하고 싶을 때 DB IP 입력 DB_ENV_FILE=.env.db DB_PORT_IN=1521 DB_PORT_OUT=1521 DB_IMAGE=registry.gitlab.com/... DB_IMAGE_VERSION=oracle-7.1.0 DB_DEFAULT_PATH=/opt/oracle/oradata # SERVICE_B Configuration SERVICE_B_IMAGE=registry.gitlab.com/... SERVICE_B_IMAGE_VERSION=latest SERVICE_B_NAME=SERVICE_B1 # SERVICE_D Configuration SERVICE_D_IMAGE=registry.gitlab.com/... SERVICE_D_IMAGE_VERSION=latest SERVICE_D_NAME=kit1 # Ports SERVICE_B_PORT_IN=8080 SERVICE_B_PORT_OUT=8080 SERVICE_D_PORT_IN=8888 SERVICE_D_PORT_OUT=8888 SERVICE_D_CACHE_PORT_IN=8900 SERVICE_D_CACHE_PORT_OUT=8900
          • .env.db 파일(DB용)

            <code />
            # DB Configuration DB_USER=admin DB_PASSWORD=0000 # Oracle ORACLE_PASSWORD=${DB_PASSWORD} DB_NAME="xe" DB_SCHEMA_NAME="schema" DB_KIND="oracle:thin" DB_DELIMITER="@" DB_DIALECT="org.hibernate.dialect.Oracle10gDialect" DB_DRIVER_CLASS_NAME="oracle.jdbc.driver.OracleDriver" DB_TYPE="oracle_table" TZ="Asia/Seoul"
      • Docker-Compose.yml

        • Postgres

          <code />
          # Docker compose x-SERVICE_B-common: &SERVICE_B-common env_file: ${DB_ENV_FILE} image: ${SERVICE_B_IMAGE}:${SERVICE_B_IMAGE_VERSION} container_name: SERVICE_B restart: always ports: - ${SERVICE_B_PORT_IN}:${SERVICE_B_PORT_OUT} environment: NODENAME: ${SERVICE_B_NAME} DB_PORT: ${DB_PORT_OUT} DB_HOST: ${DB_HOST} networks: inner_network: ipv4_address: 172.26.0.3 x-SERVICE_D-common: &SERVICE_D-common env_file: ${DB_ENV_FILE} image: ${SERVICE_D_IMAGE}:${SERVICE_D_IMAGE_VERSION} container_name: SERVICE_D restart: always ports: - ${SERVICE_D_PORT_IN}:${SERVICE_D_PORT_OUT} - ${SERVICE_D_CACHE_PORT_IN}:${SERVICE_D_CACHE_PORT_OUT} environment: NODENAME: ${SERVICE_D_NAME} DB_PORT: ${DB_PORT_OUT} DB_HOST: ${DB_HOST} networks: inner_network: ipv4_address: 172.26.0.4 x-database-common: &database-common env_file: ${DB_ENV_FILE} image: ${DB_IMAGE}:${DB_IMAGE_VERSION} container_name: db_instance restart: unless-stopped ports: - ${DB_PORT_IN}:${DB_PORT_OUT} volumes: - db_storage:${DB_DEFAULT_PATH} - package_storage:${WORKDIR} networks: inner_network: ipv4_address: 172.26.0.2 volumes: db_storage: driver: local package_storage: driver: local networks: inner_network: ipam: driver: default config: - subnet: 172.26.0.0/16 services: database: <<: *database-common healthcheck: test: [ "CMD", "pg_isready", "-U", "postgres" ] interval: 10s timeout: 3s retries: 3 profiles: ["in_db"] SERVICE_B: <<: *SERVICE_B-common depends_on: database: condition: service_healthy profiles: ["in_db"] SERVICE_D: <<: *SERVICE_D-common depends_on: database: condition: service_healthy profiles: ["in_db"] SERVICE_B_external_db: <<: *SERVICE_B-common profiles: ["ex_db"] SERVICE_D_external_db: <<: *SERVICE_D-common profiles: ["ex_db"]
        • Oracle

          <code />
          # healthcheck 부분만 아래와 같이 바꿔주면 된다. healthcheck: test: su - oracle -c "sqlplus SELECT INSTANCE_NAME, STATUS FROM V$$INSTANCE;"
    • Compose 동작 확인

      <code />
      # 컨테이너 내부 DB로 실행하는 경우 docker compose --profile in_db up -d # DB 컨테이너에서 스크립트 실행 docker exec -it db_instance initdb # 종료 # -v : 컨테이너 볼륨도 같이 제거 docker compose --profile in_db down [-v]
      <code />
      # 외부 DB와 연결하여 실행하는 경우 docker compose --profile ex_db up -d # 종료 # -v : 컨테이너 볼륨도 같이 제거 docker compose --profile ex_db down [-v]

3. Q & A

  • Compose CLI 커맨드 --env-file 옵션 vs Compose파일의 env_file 옵션의 차이가 뭔가요?

    • --env-file : Docker compose 파일 안에서 env파일을 환경변수로 참조할 수 있게 해준다.

    • env_file : 해당 env파일의 환경변수는 compose 파일에서 참조할 수 없으며, Dockerfile 내에서만 유효하다.

      <code />
      # env_file 옵션은 아래와 같은 효력을 지닌다. docker run --env-file=FILE …
  • 오라클 DB에러가 발생했어요, ORA-01031 : insufficient privileges

    DBA 권한이 없는 유저로 접속시도한 경우에 발생합니다.

    sqlplus / as sysdba 와 같이 관리자 모드로 실행하면 됩니다.

    <code />
    su - oracle -c "echo \"create user $DB_USER identified by $DB_PASSWORD;\" | sqlplus / as sysdba"
  • 오라클 DB에러가 발생했어요, ORA-01109: database not open

    데이터베이스 읽기, 쓰기 권한이 열려있지 않은 경우에 발생하는 에러입니다

    아래 명령어를 통해 Open 상태로 변경할 수 있습니다.

    <code />
    sqlplus / as sysdba ALTER DATABASE OPEN;

    하지만, 제 경우에는 Oracle DB가 완전히 기동되기 전에 실행하려해서 발생했습니다.

    아래와 같은 명령어로 기동 상태를 확인할 수 있습니다.

    <code />
    sqlplus SELECT INSTANCE_NAME, STATUS FROM V$$INSTANCE;
    <code />
    su - oracle -c "echo \"create user $DB_USER identified by $DB_PASSWORD;\" | sqlplus / as sysdba"
  • 컨테이너를 계속 켜놓고 싶어요

    <code />
    # keep running ENTRYPOINT ["tail", "-f", "/dev/null"]
  • 리눅스에서 특정 파일을 찾고 싶어요

    <code />
    # find ${경로} -name ${이름} -type [d : 디렉토리][f : 파일] find / -name test -type d
  • 쉘 스크립트에서 Bad interpreter 오류가 발생합니다

    윈도우와 리눅스 개행문자 차이로 인해 발생하는 문제입니다.

    git 에서 CRLF 개행 문자 차이로 인한 문제 해결하기 (lesstif.com)


4. 개선할 점

  • health check 스크립트 통일

    <code />
    #! /bin/sh # Wait for PostgreSQL until nc -z -v -w30 "$DB_HOST" 5432 do echo 'Waiting for PostgreSQL...' sleep 1 done echo "PostgreSQL is up and running"

참고자료

DinD(docker in docker)와 DooD(docker out of docker) | 아이단은 어디갔을까 (aidanbae.github.io)

.gitlab-ci.yml 파일에 Docker 이미지 빌드 단계 추가 - GitLab CI Workshop (infograb.io)

CI/CD 프로세스 구축기 2. 파이프라인 구성 | by kyeong su kim | 월요일 오후 9시 | Medium

GitLab Runner 를 사용하여 GitLab CI 구성하기 (tistory.com)

[Gitlab-CI/CD] window에서 Gitlab CI/CD를 docker로 배포하는 방법 (tistory.com)

[GitLab] docker-compose를 이용하여 GitLab Runner추가하기 (tistory.com)

[Gitlab] CI/GitLab Container Registry (tistory.com)

Docker Bridge Network 의 함정 (velog.io)

profile

주녁, DevNote

@junwork

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!