SOPT 34기 서버파트/Docker 스터디

[Docker] 도커 실무에 적용하기

softmoca__ 2024. 5. 8. 11:29
목차

 

1. 레이어 관리

- 이미지를 빌드할 때 레이어는 도커 파일의 지시어 한 줄당 새로운 레이어 하나가 추가 된다.

- 불필요한 레이어가 많아지면 이미지의 크기가 늘어나고 빌드 속도가 느려질 수 있다.

 

레이어가 늘어나면 이미지의 크기를 증가시키고 빌드 속도가 느려지며 이미지 관리가 복잡해 지게 된다.

 

레이어의 개수를 관리하는 법

1.  RUN

- RUN 지시어는 &&을 활용해 최대한 하나로 처리한다.
- 불필요한 명령어를 추가해서 레이어의 개수를 늘리지 않는다.

 

 

RUN 지시어를 각각 사용한 이미지는 레이어가 총 5개 추가 되었고 모든 명령어를 하나의 RUN 지시어에 모아서 실행한 이미지는 레이어가 1개만 추가 되었다.

 

2.  alpine OS를 베이스 이미지로 사용

 

 

3.  .dockerignore 파일 사용해서 불필요한 파일 제거 

 

보통 COPY 명령을 사용할 때 단일 파일이 아니라 디렉터리 전체를 복사하는 방식으로 사용한다.

이렇게 COPY . . 을 하면 루트 디렉터리에 있는 파일들을 빌드 컨텍스트로 모두 이동시키는 케이스가 많다.

COPY 지시어를 여러번 나누어서 사용하면 불필요한 레이어의 개수가 늘어날 수 있기 때문에 COPY 지시어는 그대로 사용하면ㄴ서 .dockerignore 파일을 사용해서 빌드 컨텍스트로 이동할 파일을 관리할 수 있다.

 

 

 

 스크래치 이미지를 사용하는 빌드 실습

FROM golang:alpine AS builder
WORKDIR /app
COPY main.go . ---------------- 소스코드 복사
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o helloworld main.go
-------------- 애플리케이션 빌드
FROM scratch
COPY --from=builder /app/helloworld .
EXPOSE 8080
ENTRYPOINT ["./helloworld"]
------------------------- 빌드스테이지에서 애플리케이션 복사
------------------------- 애플리케이션 실행
0. 변경사항 초기화
git reset --hard HEAD && git clean -fd
1. 실습 폴더 easydocker/build 이동 및 브랜치 변경
git switch 02-practice
2. 실습 폴더 easydocker/build/05.go-scratch 이동 및 브랜치 변경
cd 05.go-scratch
3. 이미지 빌드
docker build -t helloworld .
4. 컨테이너 실행
docker run -d -p 8080:8080 --name go-helloworld helloworld

 

이미지의 크기를 작게 구성하는 데 있어서 정적 바이너리 파일로 빌드할 수 있는 고언어를 사용하는 것은 아주 좋은 방법이다.

 

 

2. 캐싱을 활용한 빌드

캐시는 시간이 걸리는 작업의 결과물을 미리 저장해두고 동일한 작업이 발생했을 때 다시 계산하지 않고 결과를 빠르게 제공해주는 기술이다.

 

 

- Docker는 각 단계의 결과 레이어를 캐시처리 하며 지시어가 변경되지 않으면 다음 빌드에서 레이어를 재사용한다.

즉, 소스 코드르 변경하여 COPY 지시어에서 . . 부분이 변경되었다.

- COPY, ADD 명령의 경우 빌드 컨텍스트의 파일 내용이 변경되어도 캐시를 사용하지 않는다.
- 레이어가 변경되면 그 레이어와 이후의 모든 레이어는 캐시를 사용하지 않고 새로운 레이어가 만들어진다.

 

 

 

 

- 잘 변경되지 않는 파일들을 아래 레이어에 배치하면, 캐시를 활용하는 빈도를 높일 수 있다.
-
package.json, package-lock.json 파일은 소스 코드가 의존하는 외부 라이브러리 정보가 있으며 개발 시 자주 변경되지 않는다.
-
package.json, package-lock.json 파일이 변경되지 않을 경우 npm ci 단계(의존 라이브러리 설치)까지 캐시를 활용할 수 있다.

 

 

 

캐시 유뮤 시간 차이 비교

docker build -t leafy-front:2.0.0 . --no-cache

 

캐시를 사용하지 않을 겨우 docker를 빌드하는 데 21초 npm을 다운받는데 8 초가 걸렸다.

docker build -t leafy-front:2.0.1 .

캐시를 사용하니 빌드 속도가 21초에서 1.7초로 압도록으로 빠르게 빌드가 된다.

 

 

3. 코드 일부분 수정 후 다시 빌드 시도, 시간 체크 (src/App.vue)
docker build -t leafy-front:2.0.2 .

코드를 수정하니 COPY 지시어 부터는 캐시를 사용하지 못하고 다시 빌드를 실행한다.

 

 

dockerfile 개선

 

COPY package.json .
---------------- 라이브러리 설치에 필요한 package.json 파일과 package-lock.json 파일만 복사
COPY package-lock.json .
RUN npm ci --------------------------------- 의존 라이브러리 설치

이렇게 카피를 여러 번 쪼개서 라이브러리 설치 단계와 애플리케이션 빌드 과정을 분리했습니다. 첫 번째 단계에서는 라이브러리 설치에 필요한 package.json 파일을 복사했고요. 그리고 npm ci 명령을 사용해서 외부 라이브러리를 설치했습니다.

그리고 전체 소스 코드를 복사한 다음에 npm run build 명령을 실행해서 소스 코드를 애플리케이션으로 빌드했습니다.

 

 

 

4. 다음 페이지의 도커파일을 참고해 도커파일 수정 및 빌드
docker build -t leafy-front:2.0.3 . --no-cache

총 23초가 걸렸다.

5. 코드 일부분 수정 후 다시 빌드 시도, 캐시 활용 한 시간 체크
docker build -t leafy-front:2.0.4 .

의존성 설치 부분까지는 Cache를 활용하는 것을 확인할 수 있고 라이브러리에는 변경 사항이 없기 때문에 애플리케이션 빌드 단계부터 새로운 레이어가 추가되는 것을 확인할 수 있다.

총 12 초로 절반 가량 시간이 줄어 들었다.

 

백엔드 dockerfile

# 빌드 이미지로 OpenJDK 11 & Gradle을 지정
FROM gradle:7.6.1-jdk11 AS build

# 소스코드를 복사할 작업 디렉토리를 생성
WORKDIR /app

# 라이브러리 설치에 필요한 파일만 복사
COPY build.gradle settings.gradle ./

RUN gradle dependencies --no-daemon

# 호스트 머신의 소스코드를 작업 디렉토리로 복사
COPY . /app

# Gradle 빌드를 실행하여 JAR 파일 생성
RUN gradle clean build --no-daemon

# 런타임 이미지로 OpenJDK 11 JRE-slim 지정
FROM openjdk:11-jre-slim

# 애플리케이션을 실행할 작업 디렉토리를 생성
WORKDIR /app

# 빌드 이미지에서 생성된 JAR 파일을 런타임 이미지로 복사
COPY --from=build /app/build/libs/*.jar /app/leafy.jar

EXPOSE 8080 
ENTRYPOINT ["java"] 
CMD ["-jar", "leafy.jar"]

build.gradle 파일과  setting.gradle 파일에 라이브러리 설치에 필요한 정보들이 들어 있다.

RUN gradle dependencies --no-daemon

위 명령어로 라이브러리를 설치할 수 있다.

RUN gradle clean build --no-daemon

위 명령어로 애플리케이션 빌드

 

 

 

docker build -t leafy-backend:2.0.0 . --no-cache

총 128초가 걸렸다.

 

2. 소스파일 변경 후 재빌드 캐시 활용하여 단축된 시간 확인
docker build -t leafy-backend:2.0.1 .

 

총 13 초로 압도적으로 빠르게 시간이 단축 되었다.

 

 

 

3. Nginx 설정 커스터마이징, 3Tier 아키텍처 구성

Nginx 웹 서버 이미지 빌드를 할시 설정을 변경하여 Leafy 애플리케이션에  3Tier 아키텍처를 적용해서 보완적으로 더 뛰어난 구조로 구성해보자.

이전 애플리케이션 아키텍처 구성

위와 같이 클라이언트가 백엔드에 직접 요청을 보내게 되면 백엔드 애플리케이션이 외부에 노출이 되어 개발자가 의도 하지 않은 API를 호출할 위험이 있다.

 

NGINX로 개선한  애플리케이션 아키텍처 구성

- Nginx의 프록시 기술을 활용해 보안에 뛰어난 3Tier 아키텍처를 구성할 수 있다.
- Nginx는 특정 경로로 온 요청(/api로 시작하는 경로)를 지정한 서버로 전달한다.
- Nginx를 프록시 서버로 활용하여 보안 향상, 부하 관리 및 API 응답 캐싱을 활용할 수 있다.

location /api/ {
    proxy_pass http://leafy:8080;
}

위 구조에서는 백엔드에 대한 요청은 웹서버를 통해서만 접근이 가능하다.

Nginx의 프록시 기술을 활용하여 API 이외의 경로로 접근하는 벡엔드로의 접근을 물리적으로 차단할 수 있다.

 

import axios from 'axios';

const api = axios.create({
    baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:8080'
});

export default api;

 

웹서버의 입장에서 대신 표현하면 내가 응답으로 주는 파일에 백엔드 서버의 주소가 있으니까 파일을 받은 너가 알아서 접근해서 데이터를 받아오라고 하는 것이다.

 

 

import axios from 'axios';

const api = axios.create({
});

export default api;

이 주소를 없애고 proxy로 설정을 하는 부분은 주소는 상관없이 모든 요청을 나한테  보내면 백엔드 관련 요청은 웹서버인 내가 대신 백엔드 서버로 전달해 줄게 라는 방식으로 수정하는 것이다.

 

/easydocker/leafy/leafy-frontend.nginx.conf

server {
    listen       80;
    server_name  _;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    location /api/ {
        proxy_pass http://leafy:8080;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

 

front/dockerfile

# 빌드 이미지로 node:14 지정 
FROM node:14 AS build

WORKDIR /app

# 라이브러리 설치에 필요한 파일만 복사
COPY package.json .
COPY package-lock.json .

# 라이브러리 설치
RUN npm ci

# 소스코드 복사
COPY . /app

# 소스코드 빌드
RUN npm run build

# 프로덕션 스테이지
FROM nginx:1.21.4-alpine 

COPY nginx.conf /etc/nginx/conf.d/default.conf

# 빌드 이미지에서 생성된 dist 폴더를 nginx 이미지로 복사
COPY --from=build /app/dist /usr/share/nginx/html

EXPOSE 80
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
COPY nginx.conf /etc/nginx/conf.d/default.conf

 

소스코드의 nginx.conf 파일을 이미지 빌드시 nginx 설정으로 복사.

그러면 nginx가 실행될 때 위에서 작성한 소스코드상에 작성된 nginx 설정대로 실행하게 된다.

 

NGINX 적용 애플리케이션 구동 테스트

1. 이미지 빌드
docker build -t leafy-front:3.0.0-proxy .
2. network 가 제대로 생성되어 있는지 확인
docker network create leafy-network

 

 docker run -d --name leafy-postgres -v mydata:/var/lib/postgresql/data --network leafy-network devwikirepo/leafy-postgres:1.0.0

데이터베이스 컨테이너 실행

docker run -d -e DB_URL=leafy-postgres --name leafy --network leafy-network devwikirepo/leafy-backend:1.0.0

이전에는 8080으로 포트를 외부로 열었었지만 이제는 nginx의 프록시를 통해 접근할것이기 때문에 굳이 백엔드 애플리케이션을 외부에 오픈할 필요가 없어 졌다.

docker run -d -p 80:80 --name leafy-front --network leafy-network leafy-front:3.0.0-proxy

프론트엔드 컨테이너 실행

 

포트 포워딩 설정이 되어 있는 서버는 웹서버만 있기 때문에 접근 할수 있는 서버는 웹서버로 제한되어 있다.

 

 

 

정리

 

 

 

 

 

/easydocker/leafy/leafy-frontend.nginx.conf

    location /api/ {
        proxy_pass http://leafy:8080;
    }

그런데 이 설정 파일을 보면 leafy라는 주소가 그대로 적혀 있다.

데이터베이스 URL을 접근할 때는 -e option을 사용해서 leafy-postgres라는 주소를 제공했다.

 

왜냐면 이 주소가 환경마다 바뀔 수 있기 때문에 이 주소를 컨테이너를 실행하는 시점에서 환경 변수로 제공해 주었다.

이에 반해서 nginx 설정은 leafy 라는 주소가 그대로 박혀 있기 때문에 이 컨테이너의 이름이 변경되면 소스 코드도 같이 수정해야 한다.

 

그래서 nginx.conf 파일의 내용을 환경 변수를 활용해서 동적으로 구성하는 방식에 대해 알아보자.

4. 환경 변수를 활용한 동적 서버 설정

- Nginx 서버 설정에 백엔드 애플리케이션의 주소가 고정되어 있다.

- 환경 별로 Nginx가 프록시 해야 하는 주소가 바뀔 수 있다.
- 프록시 설정의 주소를 바꾸기 위해 이미지 빌드를 다시 해야 한다.

 

설정의 내용을 보면 도메인 명인 leafy와 포트 8080이 설정 파일의 고정 값으로 들어가있다.

그런데 만약 이 상태에서 leafy 8080 주소가 leafy-backend의 8080으로 변경되면 어떻게 해야 할까?

먼저 소스 코드 안에 있는 nginx.conf 파일을 수정해야 하고 소스의 내용이 변경되었기 때문에 이미지를 다시 빌드해야 한다.

 

그리고 만약에 개발 환경에는 백엔드 서버의 주소가 repeat-dev이고 운영 환경에서는 repeat-prod라는 이름으로 실행된다면 하나의 이미지로 두 가지 경우를 모두 해결할 수 없게 된다.

이렇게 배포가 되는 환경별로 달라질 수 있는 값들은 이미지를 컨테이너로 실행하는 시점에서 환경 변수를 지정하는 방식으로 활용할 수 있다.

 

 

 

 

이전에 스프링 부트 컨테이너를 실행할 때도 -e 옵션을 주어서 데이터베이스 접속 정보를 입력했었다.

데이터베이스 접속 정보는 배포되는 환경별로 충분히 달라질 수 있는 부분이다.

그래서 접속 정보를 소스 코드에 직접 입력해두는 것이 아니라 애플리케이션이 실행될 때 환경 변수로 주입하면서 결정하는 것이 더 유연한 애플리케이션 이미지를 설계하는 방법이다.

 

 

환경 변수 적용

- 환경 별로 달라지는 정보는 시스템 환경 변수로 처리하면 컨테이너 실행 시 결정할 수 있다.

 

 

 

이미지를 컨테이너로 실행할 때는 cmd에 있는 명령어에 따라서 웹 서버가 실행되게 되어있다.

하지만 지금 이 환경 변수가 있는 nginx.conf 파일을 그대로 설정 파일로 사용하시면 정상적으로 동작하지 않게 된다.

그래서 웹 서버가 실행되기 전에 nginx.conf 파일의 내용을 실제 환경 변수의 값으로 수정해야 하는 단계를 거쳐야 한다.

 

그래서 컨테이너를 실행하는 시점에서 웹 서버 프로그램을 실행하기 전에 스크립트를 실행해서 nginx.conf 파일의 내용을 먼저 수정하고 그 다음에 웹 서버를 실행할 것이다.

 

 

먼저  nginx.conf 파일을 etc/nginx/conf.d/default.conf.template이라는 파일로 복사한다.

이 파일은 바로 사용되는 설정 파일은 아니고 환경 변수의 내용을 변경한 다음에 설정 파일로 저장하기 위한 템플릿 역할을 하는 파일로 사용할 것이다.

 

그리고 ENV 부분에는 backend-host와 backend-port를 repy와 8080으로 지정해 준다.

이 값들은 컨테이너를 실행할 때 -e 옵션으로 지정할 수 있지만 "-e 옵션을 주지 않았을 때 사용되는 기본 값이다.

 

그리고 COPY를 통해서 docker-entrypoint.sh 파일을 user/local/bin으로 복사하고 user/local/bin으로 복사한 docker-entrypoint.sh 파일에 파일을 실행할 수 있는 권한을 준다.

 

마지막으로 ENTRYPOINT 지시어에 docker-entrypoint.sh라는 파일명을 지정해 두면 실제로 실행되는 명령은 docker-entrypoint.sh로 실행되고 cmd에 지정한 nginx-g daemon off 명령은 docker-entrypoint.sh의 옵션 값으로 제공되게 된다.

 

그러면 실제로 이 이미지를 컨테이너로 실행했을 때 자동으로 실행되는 명령어는 docker-entrypoint.sh nginx -g demon off로 실행되게 된다.

 

docker-entrypoint.sh

#!/bin/sh
set -e

# default.conf.template 파일에서 환경 변수를 대체하고 결과를 default.conf에 저장
envsubst '${BACKEND_HOST} ${BACKEND_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf

# 다음 명령어를 실행
exec "$@"

위 shell 스크립트는 사용자가 실행할 리눅스 명령어들을 하나의 파일에 모아둔 다음에 이 파일을 통해서 명령어를 한꺼번에 실행하는 방식이다.

 

파일의 내용을 보시면 먼저 set-e 값을 주어서 오류가 발생했을 때 스크립트를 중단하도록 설정했다.

다음으로 envsubst라는 명령을 사용해서 etc/nginx/conf.d/default.conf.template 파일에 있는 ${BACK_HOST}t와

${BACKEND_PORT}의 내용을 환경 변수의 값으로 수정하는 명령을 실행한다.

그리고 이 수정된 결과를 etc/ngingx/conf.d/default.conf  파일로 저장하는 명령어이다.

 

 

정리하자면 envsubst 명령을 사용해서 템플릿 파일의 환경 변수를 실제 값으로 변경해서  원하는 주소로 proxy하는 nginx의 설정 파일로 생성해주는 명령이다.

 

그리고 아까 docker 파일에서 docker-entrypoint.sh nginx -g demon off 명령을 이 엔트리 포인트의 옵션으로 주었다.

여기에 exec "$"@로 나와 있는 부분이 옵션으로 제공받은 값을 실행하는 부분이다.

 

정리하자면 docker-entrypoint.sh 파일에 nginx-demon off라는 값을 옵션으로 제공하면 먼저 envsubst 명령을 사용해서 환경 변수를 읽어서 환경 설정 파일을 만든 다음에 옵션으로 제공받은 명령어를 통해서 웹 서버를 실행시키는 것이다.

 

 

 

 

환경 변수를 활용한 동적 서버 테스트

3. 애플리케이션 구동 및 테스트, 백엔드 애플리케이션의 접속 도메인 변경
localhost:80 접속하여 정상 접속 확인
docker run -d --name leafy-postgres -v mydata:/var/lib/postgresql/data --network leafy-network devwikirepo/leafy-postgres:1.0.0 (Windows) docker run -d --name leafy-postgres -v mydata://var/lib/postgresql/data --network leafy-network devwikirepo/leafy-postgres:1.0.0 docker run -d -e DB_URL=leafy-postgres --name leafy-backend --network leafy-network devwikirepo/leafy-backend:1.0.0

이전과 달라진 부분은 컨테이너의 명이 leafy가 아니라 leafy-backend로 수정 되었다는 것이다.

 

만약에 환경변수 처리가 되어 있지 않았다면 proxy 설정에는 leafy로 되어 있기 때문에 소스코드에서 이 파일의 내용을 수정하고 새로운 이미지를 빌드해야 한다.

 

하지만  환경변수 처리를 해두었기 때문에 이 환경변수 값에 백엔드 애플리케이션의 호스트 네임인 leafy-backend를 지정해 문제 없이 사용할수 있다.

docker run -d -e DB_URL=leafy-postgres --name leafy-backend --network leafy-network devwikirepo/leafy-backend:1.0.0
docker run -d -e
BACKEND_HOST=leafy-backend -p 80:80 --name leafy-front --network leafy-network leafy-front:4.0.0-env

 

 

 

 

docker exec leafy-front cat etc/nginx/conf.d/default.conf

 

 

-e 옵션을 사용해서 제공한 leafy-backend로 덮어쓰여져 수정되어 있는 것을 확인할 수 있다.

 

 

 

 

 

 

5. PostgresSQL 이중화 DB 구성

- 단일 서버 구성 시 단일 서버에 장애가 생기면 전체 서비스의 장애로 이어진다.

- 서버 이중화(Redundancy) 구성 시 하나의 서버가 실패해도 다른 서버가 동일한 역할을 수행하여 고가용성을 보장한다.

이중화 : 같은 역할을 하는 서버가 두 대 이상 있다.

 

 

 

 

하지만 DB 서버 같은 경우는 데이터의 상태가 있기 때문에 이중화 서버를 구성하는 것이 까다롭다.

그래서 이중화 DB를 구성하는 방법은 여러 가지가 있다.

 

- 동시에 같은 볼륨을 사용하거나, 각각의 컨테이너에 별도의 볼륨을 연결할 수 있다.
- 동시에 같은 볼륨을 사용하면 구성이 간단하지만 볼륨에 문제가 생길 경우 대처가 어렵다.
- 동시에 같은 볼륨을 사용하면 볼륨의 성능에 부하가 생길 수 있다.
- 각각의 컨테이너에 별도의 볼륨을 연결하면 데이터의 싱크를 맞추는 처리를 별도로 해야 한다.

 

 

 

 DB 서버에서는 각각의 볼륨을 가진 컨테이너가 데이터를 동기화하는 두 가지 방법

- 프라이머리-스탠바이 복제 구조의 경우

프라이머리 서버에만 쓰기 작업을 수행하며, 프라이머리의 상태를 스탠바이에 복제한다.

스탠바이 서버는 읽기 전용으로만 사용되며, 읽기 전용 스탠바이 서버를 여러 대 사용할 수 있다.
- 프라이머리- 프라이머리 복제 구조의 경우

모든 서버에 읽기/쓰기 작업을 수행한다.

여러 서버에서 동시에 쓰기 작업이 일어나기 때문에 동기화 구성 작업이 복잡하다.

 

 

 

프라이머리-스탠바이 이중화 Postgres 구성

redundancy.sh

#1. 테스트용 네트워크 생성
docker network create postgres

#2. 프라이머리 노드 실행
docker run -d \
  --name postgres-primary-0 \
  --network postgres \
  -v postgres_primary_data:/bitnami/postgresql \
  -e POSTGRESQL_POSTGRES_PASSWORD=adminpassword \
  -e POSTGRESQL_USERNAME=myuser \
  -e POSTGRESQL_PASSWORD=mypassword \
  -e POSTGRESQL_DATABASE=mydb \
  -e REPMGR_PASSWORD=repmgrpassword \
  -e REPMGR_PRIMARY_HOST=postgres-primary-0 \
  -e REPMGR_PRIMARY_PORT=5432 \
  -e REPMGR_PARTNER_NODES=postgres-primary-0,postgres-standby-1:5432 \
  -e REPMGR_NODE_NAME=postgres-primary-0 \
  -e REPMGR_NODE_NETWORK_NAME=postgres-primary-0 \
  -e REPMGR_PORT_NUMBER=5432 \
  bitnami/postgresql-repmgr:15

#3. 스탠바이 노드 실행
docker run -d \
  --name postgres-standby-1 \
  --network postgres \
  -v postgres_standby_data:/bitnami/postgresql \
  -e POSTGRESQL_POSTGRES_PASSWORD=adminpassword \
  -e POSTGRESQL_USERNAME=myuser \
  -e POSTGRESQL_PASSWORD=mypassword \
  -e POSTGRESQL_DATABASE=mydb \
  -e REPMGR_PASSWORD=repmgrpassword \
  -e REPMGR_PRIMARY_HOST=postgres-primary-0 \
  -e REPMGR_PRIMARY_PORT=5432 \
  -e REPMGR_PARTNER_NODES=postgres-primary-0,postgres-standby-1:5432 \
  -e REPMGR_NODE_NAME=postgres-standby-1 \
  -e REPMGR_NODE_NETWORK_NAME=postgres-standby-1 \
  -e REPMGR_PORT_NUMBER=5432 \
  bitnami/postgresql-repmgr:15

# 4. SHELL1, SHELL2 각 컨테이너의 로그 확인
docker logs -f postgres-primary-0
docker logs -f postgres-standby-1

# 5. 프라이머리 노드에 테이블 생성 및 데이터 삽입 
docker exec -it -e PGPASSWORD=mypassword postgres-primary-0 psql -U myuser -d mydb -c "CREATE TABLE sample (id SERIAL PRIMARY KEY, name VARCHAR(255));"
docker exec -it -e PGPASSWORD=mypassword postgres-primary-0 psql -U myuser -d mydb -c "INSERT INTO sample (name) VALUES ('John'), ('Jane'), ('Alice');"

#6. 스탠바이 노드에 데이터가 동기화되어 있는지 확인
docker exec -it -e PGPASSWORD=mypassword postgres-standby-1 psql -U myuser -d mydb -c "SELECT * FROM sample;"

#7. 환경 정리
docker rm -f postgres-primary-0 postgres-standby-1
docker volume rm postgres_primary_data postgres_standby_data
docker network rm postgres

위 파일은 이중화 서버를 구성하기 위한 스크립트들이 적혀 있다.

이중화 DB를 구성하기 위해선 주어야 하는 옵션 값이 아주 다양하기 때문에 이렇게 파일로 명령어를 지정해 두었다.

명령어를 입력하는 순서는 아래와 같다.

1. 테스트용 네트워크 생성

2. 프라이머리 노드 Postgres 컨테이너 실행

3. 스탠바이 노드 Postgres 컨테이너 실행

4. SHLL1, SHELL2 각각 컨테이너의 로그 확인

5. 프라이머리 노드에 테이블 생성 및 데이터 삽입

6. 스탠바이 노드에 데이터가 동기화되어 있는지 확인

7. 환경 정리

프라이머리 스탠바이 구조를 구성하기 위한 환경 변수들을 추가로 지정한다.

이렇게 프라이머리의 호스트와 포트를 지정하고 파트너 노드 쪽에 자신의 컨테이너 명뿐만 아니라 PostgreSQL 스탠바이 컨테이너도 지정한다. 이미지는 비트나미에서 제공하는 PostgreSQL의 REP MGR 15 버전을 사용한다.

 

3. 스탠바이 노드 Postgres 컨테이너 실행
은 2번과 거의 동일하지만 컨테이너 명과 Volume의 이름이 다르다.

 

 

그럼 이제 이 두 대의 컨테이너는 프라이머리 스탠바이 관계로 연결되어서 프라이머리 서버에 새롭게 생성되는 데이터들은 자동으로 스탠바이 서버의 볼륨에 동기화되게 된다.

 

 

 

 

 

 

 

 

 

6. 컨테이너 애플리케이션 최적화

1. 컨테이너가 사용할 수 있는 리소스 사용량을 제한

docker stats (컨테이너명/ID) # 컨테이너의 리소스 사용량 조회
docker events # HOST OS에서 발생하는 이벤트 로그 조회

 

아무런 제한 없이 Nginx 컨테이너를 실행시키고 CPU와  Memory를 확인하면 0 이라고 나온다.

이때의 0은 제한이 없다는 의미이다.

위와 같이 CPU와 Memory를 제한해서 컨테이너르 실행시키면 제한된 리소스로만 실행되는 것을 확인할 수 있다.

 

 

LIMIT에 지정한 CPU보다 사용량이 초과할 경우 CPU 스로틀링 발생
- 컨테이너에 설정된 CPU LIMIT을 초과하는 CPU 사용이 감지되면, 시스템은 컨테이너의 CPU 사용을 제한 - 애플리케이션의 성능 저하 발생

LIMIT에 지정한 MEMORY보다 사용량이 초과할 경우

- OOM(Out Of Memory) Killer 프로세스가 실행되고 컨테이너가 강제로 종료

 

2. 자바 가상 머신(JVM) 튜닝

- JVM(Java Virtual Machine)은 자바를 실행할 수 있는 환경이다.
- 자바 애플리케이션이 사용할 수 있는 메모리 영역인 힙(
Heap) 메모리를 별도로 관리해야 한다.

 

- 자바 애플리케이션을 실행할 때 xmx라는 옵션으로 힛 메모리의 최대 값을 지정할 수 있다.

- JRE(Java Runtime Environment)에서 java -jar 명령어로 app.jar 일을 실행 시때 JVM 위에서 애플리케이션이 실행되게 된다. 라서 JVM 설정을 르게 세팅해야 한다.

# JVM 튜닝을 위한 환경 변수 추가
ENV JAVA_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"

JVM의 Heap 메모리를 컨테이너에 할당된 메모리에 맞추어 자동으로 조절한다.

(Java10 부터는 기본 활성화)

 

 

6. 컨테이너 내부에서 개발하기(Node.js/VSCode)

개발 환경 관리의 필요성

- 개발자의 PC마다 설치된 언어 및 라이브러리의 버전을 일치시키기 쉽지 않다.

- 새로운 개발자가 합류하면 개발 환경설정에 오랜시간이 걸리고,개발 환경버전이 변화하면 싱크를 맞추기 어려워진다.

 

 

 

- 한 명의 개발자가 여러 개의 프로젝트를 개발 할 경우, 각 프로젝트가 사용하는 언어나 라이브러리의 종류가 다를수 있다. 여러 개의 환경을 개발자의 PC 한 대 에서 관리하기는 어렵다.

- 언어 및 라이브러리의 버전이 일치하지않을 경우 “내컴퓨터에서는 동작해“와 같은 문제가 발생 할 수있다.

 

- 도커를 활용하면 개발자의 PC들과 배포 서버의 환경을 완전히 일치시킬 수 있다.
- IDE의 원격 개발 환경기능을 통해 각 컨테이너 별로 격리된 개발환경을 구성할수있다.

- 개발자의 PC에 갖가지 라이브러리나 언어가 설치되지 않아 깔끔하게 유지할 수 있다.

 

 

 

 

 

 

 

easydocker/leafy/leafy-frontend/.devcontainer/devcontainer.json

{
    "name": "Leafy-frontend project based node.js",
    "dockerFile": "Dockerfile",
    "forwardPorts": [80],
    "customizations": {
        "vscode": {
            "settings": {},
            "extensions": [
                "dbaeumer.vscode-eslint"
            ]
} },
    "postCreateCommand": "npm install",
    "remoteUser": "node"
}

 

easydocker/leafy/leafy-frontend/.devcontainer/Dockerfile

FROM node:14
RUN apt update && apt install -y less man-db sudo
ARG USERNAME=node
RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    && chmod 0440 /etc/sudoers.d/$USERNAME
ENV DEVCONTAINER=true

 

 

 

 

 

7. JAVA 개발환경 컨테이너 활용(IntelliJ)

 

.. 이후 체크

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

https://www.inflearn.com/course/lecture?courseSlug=%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%89%AC%EC%9A%B4-%EB%8F%84%EC%BB%A4