Linux 컨테이너 리허설
100분 안팎
학습 목표
이미지·볼륨·실행 계정과 설정 주입을 구분합니다.
개념
컨테이너로 Linux 차이를 드러냅니다
개발 PC의 파일 경로와 사용자 권한에 기대던 동아리 API를 Linux 실행 환경에 놓으면 실패 조건이 드러납니다. 컨테이너는 앱을 독립된 파일 시스템과 프로세스 환경에서 실행하도록 묶는 도구입니다. Linux 커널과 런타임 자원은 호스트 환경과 연결되며 모든 운영 조건을 대신 재현하는 가상 서버라고 단정하지 않습니다. 이번 목표는 이미 검증한 jar를 비밀값 없는 이미지로 만들고 전용 실행 UID, 외부 설정 파일, 영속 DB 볼륨을 명시하여 HTTP 계약을 확인하는 것입니다.
이미지와 컨테이너의 수명을 다르게 봅니다
이미지는 실행 파일과 규약을 담는 바탕이고 컨테이너는 그 이미지에서 실행된 인스턴스입니다. Dockerfile을 바꾸면 새 이미지를 만들어야 하지만 환경변수와 마운트는 새 실행 인스턴스의 설정입니다. 컨테이너 내부의 쓰기 계층에 H2를 저장하면 인스턴스를 삭제할 때 데이터도 잃을 수 있습니다. jar를 이미지에 넣는 COPY와 실행 시 데이터 디렉터리를 붙이는 mount는 서로 다른 단계입니다. 재기동 후 대여 이력이 남는지 확인해야 데이터 경로를 제대로 분리했다는 근거가 됩니다.
빌드는 좁은 문맥에서 진행합니다
Dockerfile의 베이스는 eclipse-temurin:17-jre입니다. 실행에는 JRE만 필요하므로 Maven이나 소스를 이미지 안에서 빌드하지 않습니다. COPY 대상은 만들어 둔 jar와 run.sh로 제한합니다. .dockerignore는 설정 파일과 기타 소스가 빌드 문맥에 넘어가지 않게 합니다. COPY .로 작업 폴더 전체를 넣으면 현재 필요 없어 보이는 인증정보나 테스트 파일까지 포함될 수 있습니다. 민감 파일을 넣었다가 다음 레이어에서 삭제하는 방식도 이전 레이어에 남은 내용을 제거하는 보장으로 쓰지 않습니다.
태그의 편리함과 재현성의 한계를 압니다
17-jre 태그는 Java 주 버전을 나타내지만 레지스트리에서 가리키는 이미지 내용이 미래에도 고정된다는 뜻은 아닙니다. 이 실습은 허용된 태그를 미리 준비하고 --pull=false로 자동 새 다운로드를 피합니다. 제출 기록에는 실제 사용한 이미지 ID와 가능하면 digest를 함께 적습니다. 다른 환경에서 같은 태그로 다시 받으면 다른 패치가 올 수 있음을 설명합니다. 가격이나 지원 기간 같은 변동 정보보다 이번 실행에 사용한 파일과 이미지 식별자를 증거로 남기는 것이 배포 재현에 유용합니다.
실행 계정은 숫자로 명시합니다
이미지의 /app, /data, /config를 준비하고 필요한 경로의 소유자를 10001:10001로 정합니다. USER 10001:10001은 이후 실행을 root 대신 그 UID와 GID로 하게 합니다. Linux는 파일 소유권을 숫자로 판단하므로 사람이 읽는 계정 이름이 없어도 실행할 수 있습니다. run.sh는 실행 가능하고 jar는 읽기만 가능하도록 설정합니다. starter는 USER 0:0으로 되어 있어 정책 검사에서 실패합니다. 이 줄을 고치되 쓰기 실패를 해결한다며 전체 계정을 root로 되돌리지 않습니다.
볼륨 권한은 이미지 권한과 함께 읽습니다
새 named volume을 /data에 붙이면 비어 있는 볼륨에 이미지 경로의 내용과 권한이 초기 복사될 수 있습니다. 이 리허설은 전용 새 볼륨을 생성하여 /data 소유권을 이어받게 합니다. 기존 볼륨이나 호스트 bind mount는 기존 파일의 권한이 우선하므로 같은 결과를 기대하지 않습니다. DB 생성 중 Permission denied가 발생하면 컨테이너 UID, 마운트 종류, 실제 경로의 소유권을 대조합니다. 애플리케이션이 쓰는 /data/club과 호스트의 동일 문자열 경로를 혼동하지 않습니다.
읽는 설정과 쓰는 DB를 나눕니다
APP_CONFIG=/config/application.properties는 읽기 전용 bind mount를 가리킵니다. CATALOG_JDBC_URL은 jdbc:h2:file:/data/club;LOCK_TIMEOUT=2000을 사용하여 named volume에 DB를 둡니다. 컨테이너의 /config는 설정, /data는 변경되는 상태입니다. 공개 비밀이 없는 표본 설정만 컨테이너 UID가 읽도록 644로 두며 실제 비밀 파일은 적절한 소유권과 더 좁은 모드를 설계해야 합니다. 파일을 읽을 수 없으면 CONFIG_UNREADABLE을 확인하고 내용을 통째로 로그에 출력하여 진단하지 않습니다.
포트 선언과 공개 범위는 다릅니다
EXPOSE 8080은 이미지의 문서 역할을 하며 호스트 포트를 자동으로 열지 않습니다. check.sh의 -p 127.0.0.1::8080은 호스트의 임의 포트를 루프백 주소에만 연결합니다. docker port로 배정된 번호를 읽어 HTTP 검사에 전달합니다. 내부 8080과 호스트 포트가 다른 것을 오류로 판단하지 않습니다. 사용 중인 고정 포트를 강제로 비우기보다 임의 포트를 선택합니다. 컨테이너 이름도 실행별 난수를 붙여 다른 작성자나 실습자의 인스턴스를 변경하지 않게 합니다.
준비 확인은 기동보다 좁게 정의합니다
컨테이너가 running이어도 Spring 초기화와 DB 연결은 아직 끝나지 않았을 수 있습니다. http-contract.py는 제한된 시간 동안 /books를 호출하여 HTTP 200과 JSON 목록을 확인한 뒤 다음 요청으로 갑니다. 준비 확인에 실패하면 READINESS_TIMEOUT으로 끝나며 반복 횟수를 늘리기 전에 설정, 볼륨 접근, 실행 상태를 살핍니다. 단순 TCP 연결은 서비스의 DB 준비까지 보여 주지 않습니다. 그 다음 로그인과 대여 검사가 성공해야 누적 API 계약이 새 실행 환경에서도 유지된다고 설명할 수 있습니다.
종료 순서로 파일 DB를 보호합니다
run.sh의 exec로 Java가 주 실행 프로세스가 되고 Spring의 graceful shutdown 설정으로 진행 중 요청을 정리할 시간을 줍니다. 스크립트는 docker stop의 15초 유예를 사용한 뒤 해당 컨테이너를 제거합니다. H2 파일을 공유하는 두 인스턴스를 동시에 실행하지 않고 이전 실행을 종료한 후 다음 실행을 시작합니다. 서비스가 멈추는 동안 조회가 불가능하므로 이 절차는 무중단 배포가 아닙니다. 전용 DB의 실습 리허설과 다중 인스턴스의 운영 설계를 혼동하지 않습니다.
정적 검사와 실행 검증을 구별합니다
python3 container-policy.py는 FROM, USER, COPY 범위, ENTRYPOINT 등 작성 규약을 살피는 검사입니다. 문자열이 맞아도 Dockerfile 전체 문법이나 파일 권한이 실행 가능한지는 docker build와 실제 컨테이너가 확인해야 합니다. 이 작성 환경에서는 Docker를 실행하지 못해 lab에 verify=external을 명시했습니다. 자동 검증기의 PENDING을 통과 실측으로 표현하지 않습니다. 외부 환경에서 bash check.sh를 실행한 결과를 추가 제출하고 실패한 HTTP 단계와 컨테이너 상태를 연결하여 설명합니다.
이미지 지시어의 의미는 Dockerfile 공식 문서를 기준으로 점검합니다.
따라하기
이미지의 경계를 읽습니다
starter Dockerfile과 .dockerignore를 엽니다. COPY는 jar와 실행기만 포함하는지, config 파일은 문맥에서 제외되는지 확인합니다. 외부 환경에서 사용하는 명령이며 이 작성 환경의 실행 출력은 없습니다.
cat Dockerfile
cat .dockerignore전용 실행 계정을 복원합니다
USER 0:0을 USER 10001:10001로 바꿉니다. /data 소유권과 실행 파일 읽기·실행 권한도 함께 읽습니다. 정적 검사는 정책만 확인합니다. 다음은 solution에서 직접 실행한 결과이며 컨테이너 런타임 성공을 뜻하지 않습니다.
python3 container-policy.py실행 결과
PASS: container static policy (runtime verification still required)
외부 환경에서 리허설합니다
Docker 엔진과 사전 이미지·Maven 캐시가 준비된 환경에서 실행합니다. check.sh가 어떤 컨테이너와 볼륨을 만들고 정리하는지 먼저 읽습니다. 예상 성공 문장은 README에 있지만 아직 실측 출력으로 기재하지 않습니다.
bash check.shHTTP와 저장 위치를 대조합니다
http-contract.py의 initial·candidate·rollback 검사를 읽고 같은 named volume으로 재기동하는 부분을 찾습니다. 외부 검증 보고서에 각 HTTP 상태와 오류 지점을 남깁니다. PENDING을 실제 실행 성공으로 바꾸어 적지 않습니다.
확인 문제
실습
Dockerfile의 USER TODO를 고쳐 전용 UID/GID 10001로 실행합니다. 설정은 읽기 전용 파일로, H2는 named volume으로 분리합니다. 외부 환경에서 bash check.sh를 실행하여 비루트 계정과 HTTP 계약·재기동 후 데이터를 확인합니다. 정적 검사만 성공한 기록은 런타임 성공으로 제출하지 않습니다.
실행 명령
bash check.sh
기대 결과
외부 실행 시 누적 Java 161개, 이미지 UID 10001, HTTP initial/candidate/rollback 계약 통과. 작성 환경에서는 PENDING입니다.
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 실행 환경에서 API 오류를 추적하는 순서를 설명합니다.