이미지의 개수를 작게 유지하는 방법과 이미지의 크기를 작게 만드는 방법
node 14 이미지에 총 5개의 지시어가 추가, CMD를 제외하고 4개의 이미지가 추가되었다. 기존의 14개 레이어에서 새로운 4개의 레이어를 추가해서 총 18개의 레이어로 빌드된다.
현업에서는 보통 이미지를 빌드하고 푸시하고 배포하는 과정을 모두 다른 기기에서 진행하는 경우가 많다. 새로운 버전으로 빌드한 이미지를 레지스트리에 푸시하고, 각각의 환경에서 이미지를 레지스트리로부터 다운받아 컨테이너를 업그레이드 한다.
이 과정에서 이미지는 네트워크를 통해 업로드되고, 다운로드 되기 때문에 이미지의 크기가 작은 것이 배포 속도와 네트워크 사용량에 유리하다 볼 수있다.
이미지 빌드
docker build -t helloworld .
docker run -d -p 8080:8080 --name go-helloworld helloworld
→ 이미지 크기를 작게 구성하는데 있어서 정적 바이너리 파일로 빌드할 수 있는 GO언어를 사용하는 것은 좋은 방법이다. 실제로 하나의 컨테이너의 크기를 줄이는 것이 중요한 미션 중의 하나인 MSA 아키텍처에서는 GO언어를 많이 사용한다.
캐싱은 빌드 속도를 크게 증가시킬 수 있는(빠르게 만들어주는) 기술이다. 시간이 걸리는 작업의 결과물을 미리 저장해두고, 동일한 작업이 발생했을 경우 다시 계산하지 않고 결과를 빠르게 제공해준다.
레이어를 어떻게 구성하는지에 따라서 캐싱을 활용하는 빈도가 달라질 수 있다.
도커 빌드 명령어를 실행하면 결과적으로 4개의 이미지 레이어를 추가한다. 이 과정에서 레이어들은 도커가 캐시로 저장해둔다.
그 다음빌드에서 동일한 지시어를 사용하면 이미지 레이어를 새롭게 만들지 않고, 캐시에 저장되어 있는 레이어를 그대로 사용한다. 완전히 동일한 소스코드로 다시 빌드할 경우에는 빠르게 이미지를 빌드할 수 있다.
도커는 지시어를 똑같이 작성한 경우에는 캐시에 저장되어 있는 레이어를 그대로 사용한다. (지시어만 같다고 X, 지시어로 처리하는 내용까지 같아야 한다.)
라이브러리 설치와 같은 자주 변경되지 않는 부분을 이미지 빌드 초기 단계에 구성해놓으면 캐싱을 잘 활용할 수 있다.
기존 도커 파일은 빌드 컨텍스트에서 전체 파일을 복사 → 라이브러리 설치하는 부분을 별도로 분리한다.
1. easydocker/leafy/leafy-frontend 디렉터리 이동 및 소스코드 상태 변경
git reset --hard HEAD && git clean -fd
git switch 01-dockerfile
2. 코드 변경 없이 2번 빌드 시도, 2번째 빌드에서 캐시 사용 확인
docker build -t leafy-front:2.0.0 . --no-cache
docker build -t leafy-front:2.0.1 .
3. 코드 일부분 수정 후 다시 빌드 시도, 시간 체크 (src/App.vue)
docker build -t leafy-front:2.0.2 .
4. 다음 페이지의 도커파일을 참고해 도커파일 수정 및 빌드
docker build -t leafy-front:2.0.3 . --no-cache
5. 코드 일부분 수정 후 다시 빌드 시도, 캐시 활용 한 시간 체크
docker build -t leafy-front:2.0.4 .
FROM node:14 AS build
WORKDIR /app
COPY package.json . // ---------------- 라이브러리 설치에 필요한 package.json 파일과 package-lock.json 파일만 복사
COPY package-lock.json .
RUN npm ci // --------------------------------- 의존 라이브러리 설치
COPY . /app //--------------------------------- 전체 소스코드 복사 RUN npm run build ----------------------------- 애플리케이션 빌드
FROM nginx:1.21.4-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
1. easydocker/leafy/leafy-backend 이동 및 다음 파일의 도커파일 수정 후 빌드
cd ../leafy-backend
docker build -t leafy-backend:2.0.0 . --no-cache
2. 소스파일 변경 후 재빌드, (src/main/java/com/devwiki/leafy/controller/plant/PlantController.java) 캐시 활용하여 단축된 시간 확인
docker build -t leafy-backend:2.0.1 .
FROM gradle7.6.1-jdk11 AS build
WORKDIR /app
COPY build.gradle settings.gradle ./ // ---------------- 라이브러리 설치에 필요한 build.gradle파일과 settingsgradle 파일만 복사
RUN gradle dependencies --no-daemon //----------------- 의존 라이브러리 설치
COPY . /app //--------------------------------- 전체 소스코드 복사
RUN gradle clean build --no-daemon //-------------- 애플리케이션 빌드
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=build /app/build/libs/*.jar /app/leafy.jar
EXPOSE 8080
ENTRYPOINT ["java"]
CMD ["-jar", "leafy.jar"]
→ 이미지 빌드 시 캐싱을 활용해서 할 수 있다. 캐시를 활용하기 위해서는 도커 파일을 작성할 때 자주 수정되지 않는 부분을 먼저 작성하는 것이 유리하다.
→ 이 세가지 종류의 서버가 유기적으로 상호작용하면서 하나의 애플리케이션으로 구성되는 것을 3Tier 아키텍처라고 한다. (Tier는 하나의 단계를 의미)
많은 엔터프라이즈 웹 애플리케이션이 3Tier 아키텍처를 기반으로 구성되어 있다.
여기서 가장 먼저 클라이언트의 첫번째 진입점 역할을 하는 서버가 웹 서버이다. 클라이언트가 주소창에 어떤 요청을 보냈을 때 요청한 경로에 해당하는 html, JS와 같은 정적 파일을 사용자에게 제공해준다. 정적 파일이라는 것은 어떤 사용자든 동일한 경로로 보낸 요청은 동일한 내용의 페이지를 제공한다는 의미이다.
사용자마다 다른 데이터를 제공하기 위해서는 프론트엔드에서 제공받은 파일을 활용해 클라이언트가 다시 백엔드 애플리케이션으로 요청을 보내야 한다. 백엔드 애플리케이션은 DB에 요청을 보내 영속성이 필요한 데이터를 조회하고, 저장할 수 있다.
화면의 구조를 보면 클라이언트가 백엔드 서버에 요청을 직접한다. 하지만 일반적으로 백엔드 애플리케이션은 시스템과 실제 데이터에 밀접한 연관이 있기 때문에 보안에 주의해야 하므로 사용자가 직접 접근하지 않도록 설정하는 것이 좋다.
우리가 구성한 아키텍쳐는 웹 애플리케이션 서버가 클라이언트에 그대로 노출되기 때문에 보안상 뛰어나다고 볼 수 없다. 따라서 Nginx의 Proxy 기능을 활용해 백엔드 애플리케이션으로의 접근을 제한할 것이다.
Proxy 설정을 하기 위해서는 Nginx 이미지를 빌드할 때 Nginx 서버의 설정을 변경해야 하기 때문에 이미지를 빌드할 때 서버의 설정을 변경하는 방법을 실습한다.
개선된 3Tier 구조이다. 이 구조에서는 백엔드 애플리케이션에 대한 요청은 웹서버를 통해서만 접근이 가능하다. 웹 서버는 클라이언트와 백엔드 애플리케이션의 다리역할을 하기 위해서 Proxy라는 기술을 활용한다.
Proxy는 요청을 전달해준다는 의미로, Nginx의 Proxy 기술을 활용하면 특정 경로로 접근하는 요청은 원하는 곳으로 다시 전달해 줄 수 있다. 그래서 Proxy를 활용해서 Nginx 서버의 /api로 시작되는 요청들은 일반적인 요청처럼 정적파일을 제공해주는 것이 아니라 백엔드 애플리케이션으로 다시 전달해준다.
프록시 설정을 하기 위해서는 Nginx의 설정파일에 아래를 추가하면 Nginx 서버로 전달되는 요청 중에 /api로 시작되는 모든 요청은 이 proxy pass에 적혀있는 http 프로토콜의 leafy:8080으로 요청을 다시 전달할 수 있다.
Nginx 서버 설정 변경
location /api/ {
proxy_pass http://leafy:8080;
}
http://localhost/index.html → 웹서버의 정적 파일을 응답
http://localhost/api/~ → 애플리케이션으로 요청 전달
Proxy는 특정 경로로 온 요청을 정해진 경로로 전달해준다.
이렇게 Nginx의 Proxy 기술을 활용하면 /api 이외의 경로로 접근하는 백엔드 애플리케이션으로의 접근을 물리적으로 차단할 수 있다.
추가적으로 api의 응답을 캐싱하거나 부하를 관리하고 접근 로그를 관리하는 것처럼 다양한 추가 기능을 Proxy를 동해 활용할 수 있다.
import axios from 'axios';
const api = axios.create({
~~baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:8080'~~
});
export default api;
→ 웹 브라우저에서 백엔드 애플리케이션에 대한 요청을 http://localhost:8080으로 보내는 설정, 해당 라인을 제거 후 저장한다.
easydocker/leafy/leafy-frontend/nginx.conf 파일 생성
server {
listen 80;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# / 경로에 왔을 경우 usr/share/nignx/html에 있는 index.html을 제공해준다.
location /api/ {
proxy_pass http://leafy:8080;
}
# /api 경로에 왔을 경우 proxy 기능을 활용해 repeat 8080 포드로 요청을 전달한다.
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
→ 웹 서버로 오는 요청 중 /api/로 시작하는 경로의 요청을 모두 http://leafy:8080으로 전달
새롭게 작성한 nignx 웹서버 설정파일인 nginx.conf 파일을 기존의 nginx 이미지에 덮어쓰기 하는 부분을 도커파일에 추가한다.
COPY nginx.conf /etc/nginx/conf.d/default.conf
→ 소스코드의 nginx.conf 파일을 이미지 빌드 시 nginx 설정으로 복사
1. 이미지 빌드
docker build -t leafy-front:3.0.0-proxy .
2. network 가 제대로 생성되어 있는지 확인
docker network create leafy-network
3. 애플리케이션 구동 및 테스트, 백엔드 애플리케이션을 포트포워딩 하지 않고 내부 통신만 사용하도록 설정
# postgres 컨테이너 실행
(MacOS) 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
# leafy 컨테이너 실행, DB_URL옵션으로 db서버 url을 db컨테이너 명으로 설정
# 기존엔 포트포워딩을 통해 8080 포트를 외부로 열었지만, Nginx의 Proxy를 통해 접근할 예정으로 백엔드 애플리케이션을 외부에 오픈할 필요가 없다.
docker run -d -e DB_URL=leafy-postgres --name leafy --network leafy-network devwikirepo/leafy-backend:1.0.0
# 프론트엔드 컨테이너 실행
docker run -d -p 80:80 --name leafy-front --network leafy-network leafy-front:3.0.0-proxy
docker ps
# 3개 컨테이너 확인 가능
# 포트포워딩 되어있는 서버는 웹서버뿐이기 때문에 실습PC에서 접근할 수 있는 서버는 웹서버로 제한
4. localhost:80 접속하여 정상 접속 확인, curl 명령으로 백엔드 접근 테스트(응답이 오지 않아야 정상)
curl http://localhost:8080/api/v1/users
5. 환경 정리
docker rm -f leafy-postgres leafy leafy-front
# Postgres에서 생성한 mydata 볼륨은 남아있기 때문에 볼륨도 삭제
docker volume rm mydata
웹 서버의 Proxy기능을 활용해 웹 애플리케이션으로 클라이언트가 직접 접근하는 것을 막았다. 클라이언트는 오로지 웹 서버만으로 요청을 수행하고, 웹 서버는 정적인 자료를 요청할 경우 바로 응답, api 요청이 전달될 경우에는 웹 애플리케이션으로 Proxy한다.
nginx 웹 서버 설정을 통해 /api 경로는 모두 전달되도록 설정했다.
백엔드 애플리케이션은 기존과 동일하게 요청을 받아서 데이터베이스 서버와 상호작용한 뒤 결과를 웹서버로 전달한다. 그러면 웹 서버는 결과를 클라에게 전달해준다.
→ proxy 기능을 활용해 클라이언트와 백엔드 서버가 연결되는 부분을 완전히 제거했다.
→ 네트워크 구성을 보면 기존과 차이점은 leafy container의 포트 포워딩이 삭제된 것이다.
기존엔 호스트의 8080주소를 활용해 백엔드 컨테이너와 상호작용 했다면, 프록시를 활용해 리피 컨테이너의 포트 포워딩을 제거하고, Nginx 웹서버에서만 리피컨테이너로 접근되도록 구성했다. 따라서 프론트엔드 컨테이너는 HostOS의 80포트로 접근하고, 백엔드 컨테이너는 HostOS의 80포트의 /api로 접근했을 때 Nginx 서버에서 프록시되어 접근할 수 있다. DB 컨테이너도 마찬가지로 기존과 동일하게 포트포워딩이 없기 때문에 여전히 외부에서 접근 불가능하고, 백엔드 애플리케이션을 통해서만 접근할 수 있는 구조이다.
→ 배포가 되는 환경 별로 달라지는 정보는 시스템 환경 변수로 처리하면 컨테이너 실행 시 결정할 수 있다.
SpringBoot의 경우, 이 프레임워크가 자체적으로 환경 변수를 찾아서 값을 결정해준다. 하지만 Nginx이미지 같은 경우는 이런 기능이 존재하지 않기 때문에 직접 nginx.conf 파일의 값을 수정해야 한다.
1. easydocker/leafy/leafy-frontend 디렉터리 이동 및 소스코드 상태 변경
git reset --hard HEAD && git clean -fd
git switch 03-proxy
# 또는 git switch 04-dynamicconfig
location /api/ {
~~proxy_pass http://leafy:8080;~~
proxy_pass http://${BACKEND_HOST}:${BACKEND_PORT};
}
→ 기존 nginx.conf 파일 수정
FROM node:14 AS build
WORKDIR /app
COPY package.json .
COPY package-lock.json .
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.21.4-alpine
# 소스코드의 nginx.conf 파일을 template 파일로 복사 기본 환경 변수 지정
COPY nginx.conf /etc/nginx/conf.d/default.conf.template
ENV BACKEND_HOST leafy
ENV BACKEND_PORT 8080
# 컨테이너 실행시 자동으로 실행될 스크립트 지정
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
ENTRYPOINT ["docker-entrypoint.sh"] # 스크립트 실행 처리
CMD ["nginx", "-g", "daemon off;"] # docker-entrypoint.sh의 옵션 값으로 제공됨
/etc/nginx/conf.d/default.conf.template 에 복사된 파일은 바로 사용되진 않고, 환경 변수의 내용을 변경한 후 설정파일로 저장하기 위한 템플릿 역할을 하는 파일이다.
ENV BACKEND_HOST leafy
ENV BACKEND_PORT 8080
-e 옵션으로 지정할 수 있지만, -e를 주지 않았을 때 사용되는 기본 값을 설정해준다.
→ 실제 실행되는 명령어는 docker-entrypoint.sh nginx -g daemon off;
#!/bin/sh
# 오류가 발생했을 때 스크립트를 중단하도록 설정
set -e
# default.conf.template 파일에서 환경 변수를 대체하고 결과를 default.conf에 저장
# 템플릿 파일과 환경 변수를 읽은 뒤 nginx.conf 파일을 구성하여 nginx 설정으로 복사
envsubst '${BACKEND_HOST} ${BACKEND_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
# 다음 명령어를 실행
# nginx -g daemon off 명령을 이 엔트리 포인트에 옵션으로 줬는데, "$@"가 옵션으로 제공받은 값 실행하는 부분이다.
exec "$@"
→ docker-entrypoint.sh 파일에 nginx-demonoff 라는 값을 옵션으로 제공하면 먼저 envsubst 명령을 사용해 환경 변수를 읽어서 환경 설정파일을 만든 다음에 옵션으로 제공받은 명령어를 통해서 웹 서버를 실행시킨다.
1. 이미지 빌드
docker build -t leafy-front:4.0.0-env .
2. network 가 제대로 생성되어 있는지 확인
docker network create leafy-network
3. 애플리케이션 구동 및 테스트, 백엔드 애플리케이션의 접속 도메인 변경
localhost:80 접속하여 정상 접속 확인
(MacOS) 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
docker run -d -e BACKEND_HOST=leafy-backend -p 80:80 --name leafy-front --network leafy-network leafy-front:4.0.0-env
4. 설정 파일에 환경 변수가 적용되었는지 확인
docker exec leafy-front cat etc/nginx/conf.d/default.conf
5. 환경 정리
docker rm -f leafy-postgres leafy-backend leafy-front
→ 이전과 달라진 점은 컨테이너명이 leafy가 아니라 leafy-backend로 수정된 것이다. 환경변수 처리가 되지 않았다면 proxy 설정에는 leafy가 되어있기 때문에 소스코드에서 이 파일의 내용을 수정하고 새로운 이미지를 빌드해야한다. 하지만 환경변수 처리를 했기 때문에 환경변수 값에 백엔드 애플리케이션의 HOST를 옵션으로 지정해주면 된다.
웹서버나 WAS 서버 같은 경우에 서버의 상태가 없게 관리할 수 있기 때문에 이중화를 하지만 DB 서버는 데이터의 상태가 있기 때문에 이중화 서버를 구성하는 것이 까다롭다.
프라이머리-스탠바이로 실습한다.
1. easydocker/leafy/leafy-postgres 디렉터리 이동 및 소스코드 상태 변경
git reset --hard HEAD && git clean -fd
git switch 05-redundancy
2. 테스트용 네트워크 생성
docker network create postgres
3. 프라이머리 노드 실행
docker run -d \
--name postgres-primary-0 \
--network postgres \ # Windows는 //bitnami/postgresql
-v postgres_primary_data:/bitnami/postgresql \
-e POSTGRESQL_POSTGRES_PASSWORD=adminpassword \ ---------------- postgres 사용자(슈퍼유저)의 비밀번호
-e POSTGRESQL_USERNAME=myuser \ ------------------데이터베이스 사용자 이름
-e POSTGRESQL_PASSWORD=mypassword \ ------------------ 데이터베이스 사용자 패스워드
-e POSTGRESQL_DATABASE=mydb \ ------------------데이터베이스 명
# 프라이머리 스탠바이 구조를 구성하기 위한 환경변수 추가 지정
-e REPMGR_PASSWORD=repmgrpassword \ --------- Repmgr 패스워드(레플리케이션 관리용)
-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 # 이미지는 bitnami에서 제공하는 postgresql 15버전 사용
→ 프라이머리의 호스트와 포트를 지정하고 파트너 노드 쪽에 자신의 컨테이너 명뿐만 아니라 PostgreSQL 스탠바이 컨테이너도 지정한다.
4. 스탠바이 노드(postgres-standby-1) 실행
docker run -d \
--name postgres-standby-1 \
--network postgres \ # Windows는 //bitnami/postgresql
-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 # 이미지는 bitnami에서 제공하는 postgresql 15버전 사용
→ 옵셥 값이나 사용하는 이미지는 이전의 프라이머리 컨테이너와 동일하지만 컨테이너의 이름, 볼륨 명이 다르다.
쓰기가 가능한 프라이머리 노드에서 테이블을 생성하고 데이터 삽입
5. SHELL1, SHELL2 각 컨테이너의 로그 확인
docker logs -f postgres-primary-0
docker logs -f postgres-standby-1
6. 프라이머리 노드에 테이블 생성 및 데이터 삽입
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');"
7. 스탠바이 노드에 데이터가 동기화되어 있는지 확인
docker exec -it -e PGPASSWORD=mypassword postgres-standby-1 psql -U myuser -d mydb -c "SELECT * FROM sample;"
8. 환경 정리
docker rm -f postgres-primary-0 postgres-standby-1
docker volume rm postgres_primary_data postgres_standby_data
docker network rm postgres
모든 애플리케이션은 CPU와 메모리를 사용한다. 일반적인 프로세스는 사용량이 별도로 제한되어 있지 않기 때문에 프로세스를 실행시키면 하드웨어의 모든 리소스를 사용할 수 있다. 하지만 도커는 가상화 기술이고, 컨테이너를 격리된 공간에서 실행하기 때문에 프로세스가 사용 가능한 리소스를 제한할 수 있다.
docker run
컨테이너의 리소스(CPU, 메모리, 네트워크 디스크) 사용량 조회
docker stats (컨테이너명/ID)
HOSTOS에서 발생하는 이벤트(생성, 종료 등) 로그 조회
docker events
1.리소스 제약이 없는 상태로 컨테이너 실행
docker run --help
# 일반 컨테이너 실행
docker run -d --name no-limit nginx
2.컨테이너의 메타데이터 확인
# inspect 중 Memory Cpus 필터링 -> 0으로 나옴 (제한 없음 의미)
docker inspect no-limit | grep -e Memory -e Cpus
3.리소스 제약이 있는 상태로 컨테이너 실행 (0.5 Core / 256M Memory)
docker run -d --name with-limit --cpus=0.5 --memory=256M nginx
4.컨테이너의 메타데이터 확인
docker inspect no-limit | grep -e Memory -e Cpus
5.실습 컨테이너 삭제
docker rm -f no-limit with-limit
자바 애플리케이션이 실행될 때 자바가 동적으로 할당되는 공간을 힙 메모리라한다. 객체가 많이 사용될수록 힙메모리 영역이 많이 사용된다. 이 힙메모리 사용량은 보통 애플리케이션이 실행될 때 지정하는데, 전체 서버 메모리의 50~80%를 설정하는 것이 일반적이다.
컨테이너 리미트 값이 16GB이고 JVM의 힙메모리 지정된 25%인 4GB면 애플리케이션은 자기가 실행되는 서버의 환경을 충분하게 활용하지 못하는 것이다. 하지만 컨테이너에 지정된 리미트 값은 환경마다 다를 수 있고, 사용 중에 늘릴 수도 있다. 따라서 자바의 힙 메모리를 지정할 때는 컨테이너의 리미트 값도 함께 고려해야 한다.
자바에서는 자바 애플리케이션을 실행할 때 -Xmx라는 옵션으로 힙메모리의 최대 값을 지정할 수 있다. 하지만 이렇게 고정할 경우, 컨테이너의 리미트 값이 변경될 경우에 자바의 실행 명령도 함께 수정해야 한다.
FROM openjdk:8-jre-alpine
# JVM 튜닝을 위한 환경 변수 추가
ENV JAVA_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
JVM의 Heap 메모리를 컨테이너에 할당된 메모리에 맞추어 자동으로 조절합니다.
(Java 8u131 부터 지원, Java 10 이상에서는 기본 활성화)
WORKDIR /app
COPY --from=build /app/build/libs/*.jar /app/leafy.jar
EXPOSE 8080
ENTRYPOINT ["java"]
CMD ["-jar", "leafy.jar"]