# Developer for Container Container를 빌드하는 방법을 알아봅시다. 여기서는 Dockerfile의 작성과 테스트를 위한 여러가지 명령들을 해볼 예정입니다. --- # Docker 기본 사용 먼저 스프링 프로젝트를 docker container image로 변경하고 로컬에서 실행하는 방법을 알아봅니다. ## Sample project ```bash= git clone https://github.com/sykang808/gs-spring-example.git ``` ## Local Build and test 프로젝트를 클론하여 로컬에서 java build 후 테스트를 해봅니다. ```bash= cd gs-spring-example ./gradlew bootJar sudo java -jar -Dserver.port=8080 build/libs/ecs-spring-sykang.jar ``` localhost 접근하면 "Hello Docker User"를 만날 수 있습니다. ```bash= curl http://localhost:8080 ``` :::info RestController를 이용한 다른 API를 추가해봅시다. ::: ## Local Docker build 해당 빌드 파일을 도커 이미지로 만들기 위해 Dockerfile을 작성합니다. COPY 명령어로 필요한 파일을 복사하고 빌드 산출물을 실행합니다. ### Make Dockerfile ```dockerfile= FROM openjdk:11-jdk COPY ./build/libs/*.jar app.jar EXPOSE 80 ENTRYPOINT ["java","-jar","/app.jar"] ``` 도커 이미지를 만들어봅니다. ```bash= docker build -t test . ``` 이미지가 잘 생성되었는지 확인합니다. test라는 이미지 레포지토리가 보이면 성공한 것입니다. ```bash= docker images ``` 도커에서 컨테이너를 실행합니다. ```bash= docker run -p 8080:80 --name test test ``` localhost:8080 접근하면 "hello Docker user"를 만날 수 있습니다. docker의 세부적인 변경사항을 추적해보겠습니다. ```bash= docker ps -l ``` 나온 Container id를 복사한다음 아래 명령어를 입력합니다. ```bash= docker diff "container id" ``` 변경사항을 확인할 수 있습니다. 아래명령으로 컨테이너에 직접 접속해보겠습니다. ```bash= docker exec -it "container id" /bin/sh ``` ps를 입력하여 java 어플리케이션이 별도의 프로세스로 실행되는 것을 확인해봅니다. 그리고 마지막으로 컨테이너를 종료해보겠습니다. ```bash= docker rm "container id" ``` ## Docker 경량화 컨테이너의 이미지를 최소한으로 유지하는 것이 좋습니다. 이미지를 빌드시 꼭 필요한 파일만 포함시켜 용량을 줄여야 불필요한 비용을 줄이고 확장성을 높힐 수 있습니다. 먼저 우리가 생성한 이미지의 크기를 확인하겠습니다. 아래 명령어를 입력 한 후 test 이미지의 크기를 확인합니다. ```bash= docker images -a ``` ![](https://hackmd.io/_uploads/rkw8wQ_Ln.png) 용량을 확인 한 후 Dockerfile을 아래로 변경하여 다시 빌드해보겠습니다. 베이스 이미지를 Openjdk:11-jdk에서 Openjdk:11-jre-slim로 변경하였습니다. ```dockerfile= FROM openjdk:11-jre-slim COPY ./build/libs/*.jar app.jar EXPOSE 80 ENTRYPOINT ["java","-jar","/app.jar"] ``` 변경 후 다시 빌드합니다. ```bash= docker build -t test . ``` 아래 명령어를 입력 한 후 test 이미지의 크기를 다시 확인합니다. ```bash= docker images -a ``` ![](https://hackmd.io/_uploads/rkePoD7_U2.png) 용량이 훨씬 줄어든 것을 확인할 수 있습니다. 이렇듯 꼭 필요한 베이스 이미지만 선택하여 최소한의 용량을 줄이는 것지 좋습니다. :::info 더 작은 base image로 컨테이너를 빌드해보세요. ECR public : https://gallery.ecr.aws/ Docker hub : https://hub.docker.com/search?image_filter=official&q= ::: ## 불필요한 레이어 줄이기 Docker 이미지는 각각 Dockerfile 명령어를 나타내는 읽기 전용 레이어로 구성됩니다. 각 레이어는 이전 레이어에서 변경된 델타입니다. RUN, COPY, ADD 명령어만 레이어를 생성합니다. 다른 명령은 임시 중간 이미지를 만들고 빌드 크기를 늘리지 않습니다. ## Multi-Stage를 이용한 빌드 컨테이너 이미지를 만드는 방법은 다양한 방법이 있습니다. 그중 많이 사용하는건 빌드된 바이너리를 베이스 이미지로 옮긴다음 실행시키는 방법과, 소스코드를 이미지로 옮긴 다음 빌드하고 패키징 하는 방법입니다. ```dockerfile= FROM openjdk:11-jdk AS builder COPY . ./ RUN chmod +x ./gradlew RUN ./gradlew bootJar FROM openjdk:11-jre-slim COPY --from=builder ./build/libs/*.jar app.jar EXPOSE 80 ENTRYPOINT ["java","-jar","/app.jar"] ``` 빌드를 하는 이미지와 실제 배포를 하는 이미지가 분리되어있는 것을 보실 수 있습니다. 이렇게 하면 빌드와 배포 이미지를 다르게 사용할 수 있어 필요한 파일만 패키징하여 배포용 이미지를 만들 수 있습니다. 이를 통해 최종 이미지의 크기를 늘리지 않고 중간 빌드 단계에 도구 및 디버그 정보를 포함할 수 있습니다. --- # 오픈소스를 이용한 컨테이너 빌드 ## Docker는 무엇인가요?? ![](https://hackmd.io/_uploads/BJgPPd7Un.png) Docker는 개발자가 컨테이너식 애플리케이션을 구축하고 공유하고 실행하는 데 사용하는 컨테이너화 플랫폼 및 런타임입니다. 우리가 수행한 Docker의 다양한 명령어들을 포함하여 컨테이너를 개발하고 사용하는데 필요한 도구를 포함하고 있습니다. 이것은 Docker가 단일 기술이나 도구를 의미하는게 아니라 컨테이너를 위한 전체 기술 스택이며 이 안에는 컨테이너 런타임인 containerd를 포함하여 사람이 쉽게 쓸 수 있도록 개발된 다양한 도구를 포함합니다. ## Docker는 유료인가요? 도커는 여전히 개인, 오픈소스 개발용으로 사용할때는 제한이 없습니다. 다만 회사, 상업적 사용을 위한 Docker Desktop은 비용을 지불해야합니다. ## 대안이 있을까요? :::info Finch는 Linux 컨테이너를 구축, 실행 및 게시하기 위한 새로운 명령줄 클라이언트를 만드는 오픈 소스 프로젝트입니다 ::: ### AWS Finch Docker를 대체하는 오픈소스는 다양합니다. 그중에서 AWS에서 기여중인 Finch는 컨테이너 개발을 위한 오픈 소스 클라이언트입니다. ![](https://hackmd.io/_uploads/HkPdHOXL2.png) Finch는 새로운 도구를 만드는 대신 다른 프로젝트를 쉽게 설치하고 사용할 수 있도록 하여 다른 프로젝트를 하나로 묶을 수 있는 간단한 기본 클라이언트를 제공하는 것을 목표로 합니다. Finch는 nerdctl과 통합된 간단한 클라이언트를 제공합니다. 핵심 build/run/push/pull 명령의 경우 Finch는 nerdctl에 의존합니다. 컨테이너 관리를 위해 containerd와 함께 작동하고 OCI(Open Container Initiative) 이미지 빌드를 처리하기 위해 BuildKit을 사용합니다. 이러한 구성 요소는 모두 Lima가 관리하는 가상 머신 내에서 실행됩니다. #### finch 설치하기 ```bash= brew install --cask finch ``` 설치가 완료되면 기본 시스템을 설정하기 위해 finch vm init가 한 번 필요합니다. 이 초기 설정은 보통 1분 정도 걸립니다. ```bash= finch vm init ``` finch vm을 시작합니다. ```bash= finch vm start ``` vm이 시작되면 기존 Docker client 명령어의 docker를 finch로 대체할 수 있습니다. 이는 nerdctl을 사용하고 있습니다. ```bash= finch build -t test . ``` ![](https://hackmd.io/_uploads/S1EECXD82.png) # 실행중인 도커에서 디버깅하기 가끔 우리는 실행중인 Docker 내부에서 디버깅이 필요할 때가 있습니다. remote debugging을 사용할 수 있습니다. 각 언어와 마다 remote debugging을 사용할 수 있는 옵션과 방법이 있습니다. 아래는 Java의 remote debugging을 위한 예시입니다. JAVA_OPTION으로 agentlib:jdwp=transport=dt_socket,address=*:5005,server=y,suspend=n을 추가합니다. debug server는 5005포트를 사용하겠습니다. 외부에서 접속할 수 있게 웹서버를 위한 80과 debug server를 위한 5005포트를 expose하겠습니다. ```dockerfile= FROM openjdk:11-jdk AS builder COPY . ./ RUN ./gradlew bootJar FROM openjdk:11-jre-slim COPY --from=builder ./build/libs/*.jar app.jar EXPOSE 80 5005 ENTRYPOINT ["java","-agentlib:jdwp=transport=dt_socket,address=*:5005,server=y,suspend=n","-jar","/app.jar"] ``` 다시 빌드하겠습니다. :::info 이후 명령어는 finch를 사용하겠습니다. ::: ```bash= finch build -t test . ``` 빌드가 완료되었으니 컨테이너를 실행하겠습니다. dockerfile에서 80과 5005번 포트를 expose시켰으니 host와도 같은 포트를 연결하겠습니다. ```bash= finch run -p 80:80 -p 5005:5005 --name test test ``` 이제 서버에 접속하여 디버깅을 할 수 있습니다. VSCode를 예로 들겠습니다. Lunch.json파일에 아래와 같은 구성을 추가합니다. ```json= { "type": "java", "name": "Debug (Attach)", "request": "attach", "hostName": "localhost", "port": 5005 } ``` VSCode의 디버깅 시작 버튼을 클릭합니다. 적절한 코드에 브래이킹포인트를 걸어줍니다. ![](https://hackmd.io/_uploads/SkZ6AYvLn.png) IDE가 debuging 서버와 연결됩니다. 다른 터미널을 띄워서 아래 명령어를 입력합니다. ```bash= curl localhost:80 ``` 아래 처럼 브레이크포인트에서 코드가 멈춥니다. 이제 step by step으로 디버깅을 할 수 있습니다. ![](https://hackmd.io/_uploads/SkNG19DU2.png) # 참고자료 ## CodeBuild에서 사용하는 buildspec.yml 아래 buildspec은 코드빌드가 수행하는 명령어의 집합입니다. 아래 buildspec을 통해 코드빌드는 컨테이너를 빌드하고 ECR에 push합니다. 이때 두가지 태그를 유지하는데 Git의 Commit Hashtag와 빌드 환경변수인 IMAGE_TAG의 값입니다. 이는 latest가 아닌 정확한 태그를 통해 코드버전과 컨테이너 이미지 버전을 추적하기 위함입니다. github action, gitlab pipeline에서도 아래와 비슷한 형태로 구성할 수 있습니다. ### 환경변수 AWS_ACCOUNT_ID : account 12자리 넘버 REPOSITORY_URI : repository URL IMAGE_TAG : "latest" // 가장 최신버전을 트래킹하기위한 태그 ### buildspec.yml ```yaml= version: 0.2 phases: install: commands: - yum -y install jq pre_build: commands: - echo "Starting docker daemon..." - echo "IMAGE_TAG - ${IMAGE_TAG}" - echo "AWS_ACCOUNT_ID - ${AWS_ACCOUNT_ID}" - echo "AWS_DEFAULT_REGION - ${AWS_DEFAULT_REGION}" - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com - TAG="$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" build: commands: - echo Build started on `date` - docker build -t "$REPOSITORY_URI:$IMAGE_TAG" . - docker tag "$REPOSITORY_URI:$IMAGE_TAG" "$REPOSITORY_URI:$TAG" post_build: commands: - echo Build completed on `date` - echo "Pushing Docker image to ECR" - docker push "$REPOSITORY_URI:$IMAGE_TAG" - docker push "$REPOSITORY_URI:$TAG" - printf '"Tag":"%s","RepositoryUri":"%s"' $TAG $REPOSITORY_URI $PROJECT_NAME $ARTIFACT_BUCKET > build.json ``` ### Public docker repository pull limit 베이스 docker image는 openjdk:11-jdk-alpine입니다. CodeBuild는 캐시되지 않으면 새로운 이미지를 불러옵니다. docker hub의 Limit를 피하기위해 베이스 이미지를 private ECR로 복사하여 사용하겠습니다. ECR로 이동하여 private에 openjdk레포지토리를 생성합니다. ECR push명령에 따라 로그인을 하고 아래 태그를 붙여서 push합니다. ```bash= docker tag openjdk:8-jdk ---.dkr.ecr.us-west-2.amazonaws.com/openjdk:8-jdk-alpine docker push ---.dkr.ecr.us-west-2.amazonaws.com/openjdk:8-jdk-alpine ``` 사용하는 도커 파일의 베이스 이미지를 변경합니다. ```dockerfile= FROM ---.dkr.ecr.us-west-2.amazonaws.com/openjdk:8-jdk AS builder ``` 이제 private ECR의 base이미지를 사용하기때문에 Limit를 걱정하지 않아도됩니다. 또는 ECR public gallay를 사용하세요. ```yaml= FROM public.ecr.aws/amazoncorretto/amazoncorretto:20-al2-jdk COPY ./build/libs/*.jar app.jar EXPOSE 80 ENTRYPOINT ["java","-jar","/app.jar"] ``` # 링크 - Spring Sample : https://github.com/spring-guides/gs-spring-boot-docker - Copilot primer Workshop : https://catalog.us-east-1.prod.workshops.aws/workshops/d03316be-3c29-49db-8dc3-eb196c1778c9/en-US/chapter4/content2 - Workshop for CI/CD Workshop : https://catalog.us-east-1.prod.workshops.aws/workshops/cbcd960c-a07b-40c2-a01d-1d2e7a52b945/ko-KR - 현대적 애플리케이션을 위한 컴퓨팅 인프라 활용 전략 - 이창익, AWS : https://www.youtube.com/watch?v=oWty7PTvwBE - AWS에서 컨테이너 시작하기 : https://www.youtube.com/watch?v=wxyyUrWVBZA&t=2s - EKS에서 애플리케이션 배포하기 : https://catalog.us-east-1.prod.workshops.aws/workshops/9c0aa9ab-90a9-44a6-abe1-8dff360ae428/ko-KR/110-cicd/100-cicd