Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

seaking110 님의 블로그

도커 실습 본문

Today I Learned

도커 실습

seaking110 2025. 3. 28. 22:54

도커 설치

  • 도커를 먼저 설치해보자
  • 윈도우에서 도커를 진행하기 위해선 환경을 먼저 설정하고 도커를 설치해야한다!

WSL2를 이용하기 위해

  • window 기능의 Linux용 Windows 하위 시스템을 체크
  • 다른 블로그에선 hyper -v 를 사용하나 현재 윈도우를 home을 이용하고 있어서 hyper -v는 지원하지 않았다.

도커 설치

  • sudo apt-get update
  • sudo apt-get install ca-certificates curl gnupg
# docker engine 설치
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin docker-compose

# docker 그룹에 현재 계정을 등록하여 sudo 없이 docker 명령을 사용하게 함
sudo usermod -aG docker user
sudo service docker restart

 

우분투 설치 

  • 우분투를 설치하고 wsl.exe -d Ubuntu 코드 실행으로 우분투 실행

도커는 처음이기 때문에 도커와 도커 compose를 이용하여 app과 db에 대해 배포 진행 먼저!

 

  • 먼저 도커 이미지를 만들어보면
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /app
COPY . .
RUN chmod +x ./gradlew && ./gradlew clean build



FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
ENV SPRING_PROFILES_ACTIVE=dev
ENTRYPOINT ["java", "-jar", "app.jar"]

 

  • 자동으로 빌드하고 이미지를 생성하는 코드 생성
  • 환경 파일을 env를 통해 SPRING_PROFILES_ACTIVE를 dev로 지정
  • 그 후 Compose 파일을 생성하여 Application과 DB를 동시에 이미지로 올리도록 설정
version: "3.8"

services:
  app:
    build: .
    container_name: app
    ports:
      - "8080:8080"
    depends_on:
      - db
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://localhost:3306/toongallery
      SPRING_DATASOURCE_USERNAME: db 유저 이름
      SPRING_DATASOURCE_PASSWORD: db 유저 비밀번호
      SPRING_JPA_HIBERNATE_DDL_AUTO: update
    networks:
      - my-network

  db:
    image: mysql:8.0
    container_name: db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root 비밀번호
      MYSQL_DATABASE: toongallery
    ports:
      - "3306:3306"
    networks:
      - my-network

networks:
  my-network:

 

트러블 슈팅

  • 3306 포트가 이미 사용 중이라는 예외 발생
  • 로컬에서 MYSQL 서버가 실행되어 있어 3306 포트를 사용하지 못하는 오류 발생
  • 서비스목록에서 MySQL을 찾아서 직접 종료

 

트러블 슈팅 2

  • 다이미지에서 DB와 APP은 이미지로 잘 만들어졌으나 컨테이너에서 APP만 실행이 안되는 문제 발생!
  • DB가 생성되는데 조금 더 오래 걸려서 APP이 생성되고 DB를 찾지 못해 APP이 죽고 DB가 생성되는 상황!
  • Compose에서 depends_on으로 DB가 먼저 생성되고 그 후 APP을 생성하게 만들어줬다고 생각했으나 아니었음!
  • 코드에서 always : restart로 컨테이너에 문제가 생겨 exit가 됐을 때 다시 시작하도록 진행 하지만 컨테이너가 app 에서 오류가 났다고 죽는 것은 아니기 때문에 실패 
  • dockerize를 이용
    ENV DOCKERIZE_VERSION v0.2.0  
    RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \  
        && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
    
  •  App을 timeout을 넣어 해당 시간 이후에 App을 실행하도록 처리

 

이미지 생성 완료

컨테이너 생성 완료

 

이제 EC2의 인스턴스에서 우분투에서 해당 도커를 다운 받고 배포해보자!

  • 위와 같이 인스턴스 내에 도커를 설치
  • 도커를 연결하여 docker compose를 실행 하기로 결정!

Docker-compose giving 'ContainerConfig' errors 발생

 

 강의 그대로 따라했는데 왜 안되는지 정말 정말 의문

Docker compose up으로 변경해야 된다! 강의가 예전꺼라...그런가보다!

또한 해당 run을 위해 env 파일을 하나 만들어서 환경 변수들을 저장했다!

 

배포 성공!!

 

이제 CI/CD 자동화를 진행해보자!

 

GitHub에 푸쉬했을때 자동으로 Docker 이미지로 빌드를 해보자!

  • github actions를 이용하면 손쉽게 자동화가 된다고 하여 이용해보려고 한다
  • actions의 workflow를 새로 생성

 

  • 현재 프로젝트에 맞는 Java와 Gradle을 이용하는 예를 선택하고
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle

on:
  push:
    branches: [ "dev" ]
  pull_request:
    branches: [ "dev" ]

jobs:
  build:

    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
    - uses: actions/checkout@v4
    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'

    # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies.
    # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md
    - name: Setup Gradle
      uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0

    - name: Build with Gradle Wrapper
      run: ./gradlew build

    # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html).
    # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version.
    #
    # - name: Setup Gradle
    #   uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
    #   with:
    #     gradle-version: '8.9'
    #
    # - name: Build with Gradle 8.9
    #   run: gradle build

  dependency-submission:

    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
    - uses: actions/checkout@v4
    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'

    # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies.
    # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md
    - name: Generate and submit dependency graph
      uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
  • Marketplace 에서 push를 하면 자동으로 도커 이미지로 build를 해주는 Build and push Docker images를 추가
            - name: Build and push Docker images
  # You may pin to the exact commit or the version.
  # uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4
  uses: docker/build-push-action@v6.15.0
  with:
    # List of a customs host-to-IP mapping (e.g., docker:10.180.0.1)
    add-hosts: # optional
    # List of extra privileged entitlement (e.g., network.host,security.insecure)
    allow: # optional
    # List of annotation to set to the image
    annotations: # optional
    # List of attestation parameters (e.g., type=sbom,generator=image)
    attests: # optional
    # List of build-time variables
    build-args: # optional
    # List of additional build contexts (e.g., name=path)
    build-contexts: # optional
    # Builder instance
    builder: # optional
    # List of external cache sources for buildx (e.g., user/app:cache, type=local,src=path/to/dir)
    cache-from: # optional
    # List of cache export destinations for buildx (e.g., user/app:cache, type=local,dest=path/to/dir)
    cache-to: # optional
    # Set method for evaluating build (e.g., check)
    call: # optional
    # Optional parent cgroup for the container used in the build
    cgroup-parent: # optional
    # Build's context is the set of files located in the specified PATH or URL
    context: # optional
    # Path to the Dockerfile
    file: # optional
    # List of metadata for an image
    labels: # optional
    # Load is a shorthand for --output=type=docker
    load: # optional, default is false
    # Set the networking mode for the RUN instructions during build
    network: # optional
    # Do not use cache when building the image
    no-cache: # optional, default is false
    # Do not cache specified stages
    no-cache-filters: # optional
    # List of output destinations (format: type=local,dest=path)
    outputs: # optional
    # List of target platforms for build
    platforms: # optional
    # Generate provenance attestation for the build (shorthand for --attest=type=provenance)
    provenance: # optional
    # Always attempt to pull all referenced images
    pull: # optional, default is false
    # Push is a shorthand for --output=type=registry
    push: # optional, default is false
    # Generate SBOM attestation for the build (shorthand for --attest=type=sbom)
    sbom: # optional
    # List of secrets to expose to the build (e.g., key=string, GIT_AUTH_TOKEN=mytoken)
    secrets: # optional
    # List of secret env vars to expose to the build (e.g., key=envname, MY_SECRET=MY_ENV_VAR)
    secret-envs: # optional
    # List of secret files to expose to the build (e.g., key=filename, MY_SECRET=./secret.txt)
    secret-files: # optional
    # Size of /dev/shm (e.g., 2g)
    shm-size: # optional
    # List of SSH agent socket or keys to expose to the build
    ssh: # optional
    # List of tags
    tags: # optional
    # Sets the target stage to build
    target: # optional
    # Ulimit options (e.g., nofile=1024:1024)
    ulimit: # optional
    # GitHub Token used to authenticate against a repository for Git context
    github-token: # optional, default is ${{ github.token }}

 

해당 코드를 잘 조합하여 test 브렌치에 push를 하면 자동으로 이미지로 빌드가 되도록 설정!

on:
  push:
    branches: [ "test" ]
  pull_request:
    branches: [ "test" ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission to Gradle Wrapper
        run: chmod +x gradlew  # 실행 권한 부여

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0

      - name: Build with Gradle Wrapper
        run: ./gradlew build

  docker:
    needs: build  # Gradle 빌드가 끝난 후 실행
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v6.15.0
        with:
          context: .
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Show Build Summary
        run: echo "✅ Docker 이미지 빌드 및 푸시 완료!"

 

  • 이때 DOCKERHUB에서 USERNAME을 직접 사용하면 보안상의 이유로 문제가 발생!!
  • 깃허브의 Repository secrets 에 환경 변수를 추가!!

 

빌드성공!!! 이제 push를 하게 되면 자동으로 build를 하고 docker hub으로 이미지가 만들어집니다!

 

이제 DOCKER HUB에 만들어진 이미지를 GITHUB Actions를 통해 EC2로 자동 배포를 하도록 수정!

기존 gradle.yml에 배포 관련 내용 추가

deploy:
  needs: docker  # Docker 이미지 빌드 후 실행
  runs-on: ubuntu-latest

  steps:
    - name: Checkout Repository
      uses: actions/checkout@v4

    - name: Deploy to EC2
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_USER }}
        key: ${{ secrets.EC2_SSH_KEY }}
        port: 22
        script: |
          echo "🔑 SSH 인증 성공!"
          echo ${{ secrets.EC2_HOST }}
          echo ${{ secrets.EC2_USER }}
          echo "SSH Key Length: $(echo -n "${{ secrets.EC2_SSH_KEY }}" | wc -c)"
          docker stop myapp || true
          docker rm myapp || true
          docker pull ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest
          docker run -d --name myapp --env-file env -p 8080:8080 ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest

 

문제 발생

 

 

SSH : no key found 문제가 발생한다!!!!!!!!!

정말 수도 없이 다시 반복하며 마주한 문제로 하루 이상 사용했다

EC2의 우분투에서 직접 공개 키와 개인키를 만들어서도 진행 해보고 

appleboy의 버전도 바꿔보는 등 여러 방법을 진행하면서 수정한 부분이 있다.

 

  • 먼저 로그를 찍어보면 좋다라는 의견을 받아 echo를 사용해서 로그를 찍어보니 secretskey는 로그에 안찍힌다!!! 젠장
  • 먼저 윈도우에서 키의 pem 파일의 정보를 받아오기 위해 cmd 창에서 type으로 안의 내용을 가져왔는데
  • 복사를 해오니 뒤에 있는 공백까지 모두 가져오는 탓에 공백을 삭제하는 코드에 넣으니 키의 줄바꿈은 있어야한다!
  • 또한 맨위의 BEGIN 부분과 END 부분 역시 가져와야된다는 사실은 맨처음엔 몰랐다!

 

 

  • 하지만 이번엔 다른 문제가 발생했는데 SSH 인증 실패를 의미...

Ubuntu 원격에서 공개키와 개인키를 만들어서 해당 키를 이용해서 해보기도 하고

ssh-keygen

keypair도 다시 만들어보고 인스턴스의 /etc/ssh/sshd_config 설정 파일에

PasswordAuthentication yes
PubkeyAuthentication yes
PermitRootLogin no

추가해 보고 정말 많고 많은 해본 결과

인스턴스를 새로 파고 해당 keypair로 인증을 하니 거짓말 같이 해결...내 하루...

 

자동화 구현 끝~~!!!!!!!

 

 

 

발표 자료

 (전체 코드)

name: Java CI with Gradle and Docker

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission to Gradle Wrapper
        run: chmod +x gradlew  # 실행 권한 부여

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0

      - name: Build with Gradle Wrapper
        run: ./gradlew build
        
      - name: Run Tests with JaCoCo
        run: ./gradlew test jacocoTestCoverageVerification

  docker:
    needs: build  # Gradle 빌드가 끝난 후 실행
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v6.15.0
        with:
          context: .
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Show Build Summary
        run: echo "✅ Docker 이미지 빌드 및 푸시 완료!"

  deploy:
    needs: docker  # Docker 이미지 빌드 후 실행
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Deploy to EC2
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_SSH_KEY }}
          port: 22
          script: |
            echo "🔑 SSH 인증 성공!"
            echo ${{ secrets.EC2_HOST }}
            echo ${{ secrets.EC2_USER }}
            echo "SSH Key Length: $(echo -n "${{ secrets.EC2_SSH_KEY }}" | wc -c)"
            docker stop myapp || true
            docker rm myapp || true
            docker pull ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest
            docker run -d --name myapp --env-file env -p 8080:8080 ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest

 

  • 자동 빌드 및 테스트, 배포 구성 과정

 

 

2개의  gitHub actions 예시를 이용하여 구현

 

빌드 및 테스트 구현

  • Gradle 빌드 및 실행 권한 부여
- name: Grant execute permission to Gradle Wrapper
  run: chmod +x gradlew  # 실행 권한 부여

 

 

  • Gradle을 설정하고 빌드 수행
- name: Build with Gradle Wrapper
  run: ./gradlew build
  • JaCoCo를 활용한 테스트 커버리지 검증
- name: Run Tests with JaCoCo
  run: ./gradlew test jacocoTestCoverageVerification

 

Docker 이미지 빌드 및 푸시

    • Docker Hub 로그인
- name: Login to Docker Hub
  uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKERHUB_USERNAME }}
    password: ${{ secrets.DOCKERHUB_TOKEN }}
    • Docker 이미지 빌드 및 푸시
- name: Build and Push Docker Image
  uses: docker/build-push-action@v6.15.0
  with:
    context: .
    push: true
    tags: ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest

 

EC2 배포 자동화

  • EC2 인스턴스에 SSH 접속 후 컨테이너 실행
- name: Deploy to EC2
  uses: appleboy/ssh-action@master
  with:
    host: ${{ secrets.EC2_HOST }}
    username: ${{ secrets.EC2_USER }}
    key: ${{ secrets.EC2_SSH_KEY }}
    port: 22
    script: |
      docker stop myapp || true
      docker rm myapp || true
      docker pull ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest
      docker run -d --name myapp --env-file env -p 8080:8080 ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest
 
  • 코드 변경 시 어떻게 자동으로 빌드 및 테스트가 수행되는지에 대한 배포 절차

배포 절차

  1. 개발자가 코드 변경 후 GitHub에 Push → Pull Request(PR) 생성
  2. GitHub Actions가 자동으로 빌드 및 테스트 수행
  3. 테스트 커버리지 통과 시 Docker 이미지 빌드 및 Docker Hub에 푸시
  4. EC2 서버에 SSH 접속 후 최신 버전의 컨테이너를 실행
  5. 배포 완료 후 서비스 자동 재시작
  • 수동 배포와 비교하여 CI/CD 파이프라인을 적용한 후의 효율성 차이와 느낀점

수동 배포와 CI/CD 적용 후 효율성 비교

수동 배포는 개발자가 push를 하고 그걸 다시 빌드하고 Filezila를 통해 빌드한 파일을 EC2 인스턴스로 이동시켜서 직접 배포하여 실행시켜야 하며 테스트 역시 수동으로 직접 해야하고 사람이 직접하는 지라 오류가 발생할 수 있는데

CI/CD 파이프 라인을 적용함으로써 실수를 방지하고 PR을 올리면 자동으로 진행하여 빌드, 테스트 배포까지 빠르게 된다라는 점이 매우 간편하고 매력적이라고 생각합니다.

따라서 개발자는 코드 변경에만 집중하고 배포의 효율성이 굉장히 올라갔다고 생각합니다!

CI/CD 구현이 생각보다 많이 어렵고 오랜시간 걸렸지만 한번 시도해보고 하는 방법을 아니까 이제는 배포가 정말 쉬워졌다고 생각합니다!

 

build - docker - deploy까지 된 인증샷

 

'Today I Learned' 카테고리의 다른 글

캐시  (0) 2025.03.26
Docker & CICD  (0) 2025.03.26
AWS 특강  (0) 2025.03.24
plus 주차 트러블 슈팅  (0) 2025.03.21
plus 주차 개인 과제  (0) 2025.03.21