자바 애플리케이션처럼 실행 옵션이 긴 프로세스를 Deployment 로 올릴 때 자주 막히는 것들을 정리한다. 실행 명령과 인자 지정, 환경 변수 치환, 리소스 값 표기, 이미지 가져오기 실패다.
Dockerfile 의 ENTRYPOINT 는 파드 스펙의 command 로, CMD 는 args 로 덮인다. 둘 중 하나만 적으면 나머지는 이미지의 값을 그대로 쓴다. 인자가 많은 자바 실행은 나눠 적는 편이 읽기 좋다.
containers:
- name: api-server
image: registry.example.com/service/api-server:1.4.2
command: ["/usr/lib/jvm/java-11-openjdk/bin/java"]
args:
- "-Dspring.profiles.active=$(ENV)"
- "-Xms1g"
- "-Xmx1g"
- "-jar"
- "/opt/apps/api-server/lib/api-server.jar"
env:
- name: ENV
value: "dev"
파드 스펙 안에서 환경 변수를 펼칠 때는 $(VAR) 형식을 쓴다. 셸이 개입하지 않으므로 ${VAR} 는 치환되지 않고 글자 그대로 넘어간다. 치환할 변수는 같은 컨테이너의 env 에 먼저 정의돼 있어야 한다.
셸 기능(파이프·리다이렉션·&&)이 필요하면 셸을 명시적으로 부른다.
command: ["/bin/sh", "-c"]
args: ["exec java -jar /opt/apps/app.jar >> /var/log/app.log 2>&1"]
exec 를 붙여야 자바 프로세스가 PID 1 을 넘겨받아 종료 신호를 받는다.
Deployment in version "v1" cannot be handled as a Deployment:
quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'
CPU 와 메모리의 단위 표기가 잘못된 것이다.
| 자원 | 맞는 표기 | 틀린 표기 |
|---|---|---|
| CPU | "1" · 500m · 0.5 |
1s · 1core |
| 메모리 | 1Gi · 512Mi · 1G |
1GiB · 1gb |
CPU 는 코어 수이고 m 은 1/1000 코어다. 메모리의 Gi 는 2진 접두사(1024 기준), G 는 10진 접두사(1000 기준)이며 GiB 라는 표기는 받지 않는다. CPU 를 정수로 적을 때는 따옴표로 감싼다. YAML 파서가 숫자로 읽어 문제가 생기는 것을 막는다.
요청과 상한을 어떻게 잡느냐가 QoS 등급을 정하고, 그것이 노드가 압박을 받을 때 어느 파드가 먼저 쫓겨나는지를 결정한다. 자바 애플리케이션은 힙 설정과 컨테이너 메모리 상한을 함께 맞춰야 한다. 상한만 걸고 힙을 그보다 크게 잡으면 OOM 으로 강제 종료된다.
failed to pull and unpack image "...": failed to resolve reference
확인 순서는 넷이다.
이미지 이름에 레지스트리 주소가 빠지지 않았는지 본다. service/api-server:latest 는 Docker Hub 의 service 사용자 저장소로 해석된다. 사설 레지스트리라면 주소를 포함해 registry.example.com/service/api-server:1.4.2 로 적는다.
인증이 필요하면 imagePullSecrets 가 있어야 하고, 그 시크릿은 파드와 같은 네임스페이스에 있어야 한다. 다른 네임스페이스의 시크릿은 참조되지 않는다.
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=<사용자> \
--docker-password="${REGISTRY_PASSWORD}" \
-n staging
노드가 레지스트리에 닿는지, 인증서를 신뢰하는지 본다. 이 부분은 사설 레지스트리 TLS 신뢰 문제 진단 에 있다.
latest 태그와 imagePullPolicy: IfNotPresent 를 함께 쓰면 노드에 남은 옛 이미지를 계속 쓴다. 태그를 버전으로 고정하는 것이 맞고, 굳이 latest 를 쓴다면 정책을 Always 로 둔다.
시작 지점에서는 tcpSocket 이 간단하다. 애플리케이션이 헬스 엔드포인트를 제공하면 httpGet 으로 바꾼다. 가상 호스트로 라우팅하는 애플리케이션이라면 Host 헤더를 명시한다.
livenessProbe:
httpGet:
path: /api/liveness/ishealthy
port: 8080
httpHeaders:
- name: Host
value: web-api.test.net
initialDelaySeconds: 15
periodSeconds: 15
failureThreshold: 3
프로브는 클러스터 안에서 파드 IP 로 직접 호출된다. Ingress 주소로 우회해 검사하지 않으므로 외부 도메인 이름을 host 로 지정하지 않는다. 기동이 오래 걸리는 애플리케이션은 livenessProbe 의 초기 지연을 늘리기보다 startupProbe 를 따로 두는 편이 낫다.