์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

[AWS] Spring Boot ๋ฐฐํฌ

_JIONE_ 2024. 5. 14. 14:33

๐Ÿ“ข ํ•ด๋‹น ํ”„๋กœ์ ํŠธ๋Š” AWS์˜ EC2, RDS, LoadBalancer, Route53, S3์™€ Docker, Github action์„ ์‚ฌ์šฉํ•ด ๋ฐฐํฌํ•œ ๊ฒฝํ—˜์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. 

 

 

Nginx(์›น์„œ๋ฒ„)๊ฐ€ ํ•„์š”ํ•œ ์ด์œ ๋Š”?

์›น์„œ๋ฒ„๊ฐ€ ์—†๋‹ค๋ฉด DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ด์•ผํ•˜๋Š” ๋™์  ์š”์ฒญ์€ ๋ฌผ๋ก  ์ •์  ์š”์ฒญ๊นŒ์ง€ WAS ์„œ๋ฒ„์—์„œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค.

NGINX๋Š” ์ •์  ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ WAS ์„œ๋ฒ„์˜ ๋ถ€๋‹ด์„ ์ค„์—ฌ์ค€๋‹ค. ์ถ”๊ฐ€์ ์œผ๋กœ ์š”์ฒญ์„ ์–ด๋Š ์„œ๋ฒ„๋กœ ๋ณด๋‚ผ์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ ์˜ ์—ญํ• ๊ณผ ์บ์‹œ์„œ๋ฒ„์˜ ์—ญํ• , ๋ฏผ๊ฐ ์ •๋ณด๋ฅผ ์ˆจ๊ฒจ์ฃผ๋Š” ์—ญํ• , SSL ์ธ์ฆ ์ฒ˜๋ฆฌ๊นŒ์ง€ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Load Balancer๊ฐ€ ํ•„์š”ํ•œ ์ด์œ ๋Š”?

๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ๋Š” ์š”์ฒญ์„ ์—ฌ๋Ÿฌ ์„œ๋ฒ„๋กœ ๋ถ„์‚ฐ์‹œ์ผœ ์„œ๋ฒ„์˜ ๊ณผ๋ถ€ํ•˜๋ฅผ ๋ง‰๋Š”๋‹ค.

์ถ”๊ฐ€์ ์œผ๋กœ ALB์˜ ๊ฒฝ์šฐ Auto scaling๊ณผ SSL ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

Nginx์™€ Load Balancer๊ฐ€ ๋‘˜๋‹ค ํ•„์š”ํ• ๊นŒ?

ALB(Application Load Balancer)์™€ Nginx๋Š” WAS ์„œ๋ฒ„ ์ „์˜ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•ด์ฃผ ์ , ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ๊ณผ SSL ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” ์ ์—์„œ ๋น„์Šทํ•˜๋‹ค. 

๋‚˜๋Š” ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•œ ์„œ๋ฒ„๊ฐ€ 1๋Œ€์ด๊ธฐ ๋•Œ๋ฌธ์— ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ์ด ํ•„์š”ํ•˜์ง€ ์•Š์•˜๊ณ , ๋„๋ฉ”์ธ์„ ์—ฐ๊ฒฐํ•˜๊ณ  SSL ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์—์„œ Nginx์™€ ALB ์ค‘ ์–ด๋–ค ๊ฒƒ์„ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š”์ง€, ๊ผญ ๋‘˜๋‹ค ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์žˆ๋Š”์ง€ ๊ณ ๋ฏผํ–ˆ๋‹ค.

 

๊ฒฐ๋ก ๋งŒ ๋งํ•˜์ž๋ฉด, Nginx๋กœ SSL ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋ ค๋ฉด EC2์—์„œ SSL ์ธ์ฆ์„œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ๊ฐฑ์‹ ํ•˜๋Š” ์ž‘์—…์ด ํ•„์š”ํ–ˆ๋‹ค. ๋ฐ˜๋ฉด์—,  AWS Certificate Manager์—์„œ SSL ์ธ์ฆ์„œ๋ฅผ ๋ฐœ๊ธ‰๋ฐ›๊ณ  ์ด๋ฅผ ALB์— ๋ถ€์ฐฉํ•˜๋ฉด ๊ฐฑ์‹ ํ•˜๋Š” ์ž‘์—…์ด ํ•„์š” ์—†์—ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ๋‚˜๋Š” ํ›„์ž์˜ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ–ˆ๋‹ค. 

 

ํ›„์ž ์•„ํ‚คํ…์ฒ˜ ํ”Œ๋กœ์šฐ

 

ALB์—์„œ 80 ํฌํŠธ๋ฅผ ํ†ตํ•ด ๋“ค์–ด์˜ค๋Š” HTTP ์š”์ฒญ๊ณผ 443 ํฌํŠธ๋ฅผ ํ†ตํ•ด ๋“ค์–ด์˜ค๋Š” HTTPS ์š”์ฒญ์„ ๋ฐ›์•„๋“ค์ด๋„๋ก ๋ฆฌ์Šค๋„ˆ๋ฅผ ์„ค์ •ํ•œ๋‹ค. HTTPS ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด, ALB๋ฅผ ํ†ตํ•ด ๋Œ€์ƒ๊ทธ๋ฃน(EC2 ์„œ๋ฒ„์˜ ๋ฌถ์Œ)์œผ๋กœ ์ „์†กํ•œ๋‹ค. (HTTPS๋ฅผ ๋ฆฌ์Šค๋„ˆ๋กœ ๋“ฑ๋กํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” SSL ์ธ์ฆ์„œ๊ฐ€ ํ•„์š”->๋ถ€์ฐฉํ•ด๋‘์—ˆ์Œ) HTTP ์š”์ฒญ์€ 443 ํฌํŠธ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ์‹œ์ผœ HTTPS ์š”์ฒญ์œผ๋กœ ์ž๋™ ๋ณ€๊ฒฝํ•œ๋‹ค. 

 

NGINX์˜ ๊ฒฝ์šฐ SSL ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ธฐ์— 80 ํฌํŠธ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์š”์ฒญ์ด ๋“ค์–ด์˜จ๋‹ค.

๋“ค์–ด์˜จ ์š”์ฒญ์„ ํ†ฐ์บฃ ์„œ๋ฒ„์˜ ํฌํŠธ์ธ 8080์œผ๋กœ ์—ฐ๊ฒฐํ•œ๋‹ค. 

 

์—ฌ๊ธฐ์„œ HTTPS ํ†ต์‹ ์ด ์ด๋ค„์ง€๋Š” ์ดˆ๋ก์ƒ‰ ๋ถ€๋ถ„ ์™ธ์—๋Š” SSL ์ธ์ฆ์ด ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š๊ธฐ์— ๋ณด์•ˆ์ƒ์˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

๋”ฐ๋ผ์„œ, ALB์™€ ์„œ๋ฒ„๊ฐ€ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ์„ผํ„ฐ ๋‚ด์— ์žˆ๊ฒŒ ํ•˜๊ฑฐ๋‚˜ private VPC๋กœ ์—ฐ๊ฒฐํ•˜๋ฉด, ์•ˆ์ „ํ•˜๊ฒŒ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

S3 presigned URL์„ ์‚ฌ์šฉํ•œ ์ด์œ ๋Š”?

 

์ผ๋ฐ˜์ ์œผ๋กœ S3๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋น„์Šค์˜ ๊ฒฝ์šฐ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํŒŒ์ผ์„ ์—…๋กœ๋“œ/๋‹ค์šด๋กœ๋“œํ•ด์™”๋‹ค.

ํ•˜์ง€๋งŒ, ํฐํŒŒ์ผ์„ ์„œ๋ฒ„์— ๋ณด๋‚ด๊ฒŒ ๋œ๋‹ค๋ฉด 1) ์„œ๋ฒ„์˜ ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€ 2) HTTP์—์„œ ํฐํŒŒ์ผ์„ ์—ฌ๋Ÿฌ๋ฒˆ์˜ ์ž‘์€ ์š”์ฒญ์œผ๋กœ ๋ถ„ํ• ํ•˜์—ฌ ์ „์†กํ•จ์œผ๋กœ์จ ์ „์†ก ์†๋„ ๊ฐ์†Œ ๋“ฑ ์„ฑ๋Šฅ์— ์•…์˜ํ–ฅ์„ ๋ฏธ์นœ๋‹ค.

 

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด S3์—์„œ ์ œ๊ณตํ•˜๋Š” Presigned Url์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์•„๋ž˜์™€ ๊ฐ™์ด ํŒŒ์ผ์„ ์ง์ ‘์ ์œผ๋กœ ์„œ๋ฒ„์— ์˜ฌ๋ฆฌ๋Š” ๊ฒƒ์„ ๋ง‰์•˜๋‹ค. 

Presigned Url์ด๋ž€ S3 ๋ฒ„ํ‚ท์˜ ์†Œ์œ ์ž๊ฐ€ ๋ฏธ๋ฆฌ ํŒŒ์ผ ์—…๋กœ๋“œ/๋‹ค์šด๋กœ๋“œ ๊ถŒํ•œ์— ๋Œ€ํ•ด ์„œ๋ช…์„ ํ•ด์ค€๋’ค ์‚ฌ์šฉ์ž์—๊ฒŒ ํ•ด๋‹น Url์„ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. ์ด๋กœ์จ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  ํŒŒ์ผ์„ ์ €์žฅ์†Œ์— ์ง์ ‘ ์—…๋กœ๋“œ/๋‹ค์šด๋กœ๋“œ ํ•  ์ˆ˜ ์žˆ์–ด ์„œ๋ฒ„์˜ ์ž์›์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ๋‹ค.

 

cf. ์ด๋ฒˆ ๊ธ€์—๋Š” ์นด์นด์˜ค ํ† ํฐ ID ๊ธฐ๋ฐ˜์œผ๋กœ ์œ ์ €๋งˆ๋‹ค ํด๋”๋ฅผ ์ƒ์„ฑํ•œ ํ›„, ๊ทธ ํด๋”์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๋Š” ๊ณผ์ •์ด ์ถ”๊ฐ€๋˜์–ด ์žˆ๋‹ค.


์‹ค์ œ ๊ตฌ์ถ• ๊ฒฝํ—˜์„ ํ†ตํ•ด ์‚ดํŽด๋ณด์ž.

 

RDS ์„ค์ •

mySQL๊ณผ RDS ์—ฐ๋™

ํผ๋ธ”๋ฆญ์•ก์„ธ์Šค๋ฅผ ํ—ˆ์šฉํ•œ RDS ์ƒ์„ฑ

mysql workbanch์—์„œ Hostname์— RDS endpoint๋ฅผ, port์— mysql ํฌํŠธ(3306)๋ฅผ ์‚ฝ์ž…

(ํ˜„์žฌ IPv4 ํ•˜๋‚˜๋งŒ์„ ๋ฌด๋ฃŒ๋กœ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ, RDS๋‚˜ ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ ์‚ฌ์šฉ์‹œ ์ถ”๊ฐ€ IPv4๋น„์šฉ์ด ๋ฐœ์ƒ, EC2๋ฅผ ์ด์šฉํ•ด RDS ์™ธ๋ถ€์—ฐ๊ฒฐ(RDS ์—ฐ๊ฒฐ์‹œ EC2 ์ปดํ“จํŒ… ๋ฆฌ์†Œ์Šค์— ์—ฐ๊ฒฐ ์„ ํƒ ํ›„ ํผ๋ธ”๋ฆญ ์—‘์„ธ์Šค ๋น„ํ—ˆ์šฉ)์„ ์‚ฌ์šฉํ•˜๋ฉด ๋น„์šฉ ๊ฐ์†Œ ๊ฐ€๋Šฅ) 

 

 

ํด๋” ๊ตฌ์กฐ

 

 

deploy.yml ( github action์ด ๋Œ๋ฆฌ๋Š” ํŒŒ์ผ ) ์„ค์ •

Github action = ์„œ๋ฒ„์— ์ ‘์†ํ•ด docker๋ฅผ ์‹คํ–‰ + main์— ํ‘ธ์‰ฌ๋œ ์ปค๋ฐ‹์„ ๋ณต์‚ฌ = CI/CD

deploy.yml ํŒŒ์ผ์ด ์‹คํ–‰๋˜๋ฉด์„œ, ec2์— docker ์ด๋ฏธ์ง€๋ฅผ pullํ•˜์—ฌ ์ž๋™ ๋ฐฐํฌ 

name: Java CI with Gradle

on:
  push:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest # ec2 ubuntu๋กœ ๋งŒ๋“ฆ
    steps:
      - name: checkout
        uses: actions/checkout@v3

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


      ## create application.yml
      - name: make application.yml
        run: |
          
          cd ./src/main/resources
          touch ./application.yml
          
          echo "${{ secrets.APPLICATION }}" >> ./application.yml # application.yml ์ „์ฒด -> ๊ณต๋ฐฑ์ฒ˜๋ฆฌ ํ•ด๋‘˜๊ฒƒ, ๊นƒ์ด๊ทธ๋…ธ์–ด์— ์˜ฌ๋ฆฌ์ง€ ๋ง๊ฒƒ
        shell: bash


      ## gradle build
      - name: Build with Gradle
        run: |
          chmod +x ./gradlew
          ./gradlew clean build -x test


      ## ์ด๋ฏธ์ง€ ๋นŒ๋“œ ๋ฐ ๋„์ปคํ—ˆ๋ธŒ์— push
      - name: web docker build and push
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} # ๋„์ปค ํ—ˆ๋ธŒ ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ
          docker build -t ${{ secrets.DOCKER_REPO }}/docker-web . # ๋„์ปค ํ—ˆ๋ธŒ ๋ ˆํฌ ์ด๋ฆ„
          docker push ${{ secrets.DOCKER_REPO }}/docker-web 
          docker build -f ./config/nginx/Dockerfile -t ${{ secrets.DOCKER_REPO }}/docker-nginx .
          docker push ${{ secrets.DOCKER_REPO }}/docker-nginx

      - name: Create Remote Directory
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ubuntu
          key: ${{ secrets.KEY }}
          script: mkdir -p ~/srv/ubuntu # ec2์ƒ์— ๊ฒฝ๋กœ ์„ค์ •

      - name: copy source via ssh key
        uses: burnett01/rsync-deployments@4.1
        with:
          switches: -avzr --delete
          remote_path: ~/srv/ubuntu
          remote_host: ${{ secrets.HOST }} # ec2 DNS ์ฃผ์†Œ
          remote_user: ubuntu
          remote_key: ${{ secrets.KEY }} # pem ํ‚ค

      ## ์ด๋ฏธ์ง€ pull ๋ฐ docker compose up
      - name: executing remote ssh commands using password
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ubuntu
          key: ${{ secrets.KEY }}
          script: |
            sh ~/srv/ubuntu/config/scripts/deploy.sh
            cd /home/ubuntu/srv/ubuntu/
            sudo chmod 666 /var/run/docker.sock
            sudo docker rm -f $(sudo docker ps -qa)
            sudo docker pull ${{ secrets.DOCKER_REPO }}/docker-web
            sudo docker pull ${{ secrets.DOCKER_REPO }}/docker-nginx
            docker-compose up -d  # docker-compose.yml ์‹คํ–‰
            docker image prune -f

 

APPLICATION ํŒŒ์ผ์—๋Š” application.ymlํŒŒ์ผ์˜ ๋‚ด์šฉ์„, HOST ํŒŒ์ผ์—๋Š” EC2 ip ์ฃผ์†Œ๋ฅผ, KEY ํŒŒ์ผ์—๋Š” .pemํ‚ค๋ฅผ ์‚ฝ์ž…

 

 

Jetbrain ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ • 

${} ์•ˆ์˜ ๋‚ด์šฉ์€ ์ฑ„์›Œ ๋„ฃ์„ ๊ฒƒ 

๋กœ์ปฌ์—์„œ ์ž‘์—…ํ•œ application.yml์—์„œ ์ค‘ ์•„๋ž˜ ๋‚ด์šฉ์„ ๊ทธ๋Œ€๋กœ ๊นƒํ—ˆ๋ธŒ์— ๋…ธ์ถœ์‹œํ‚ค๋ฉด ํ•ดํ‚น์œ„ํ˜‘์ด ์žˆ์œผ๋ฏ€๋กœ,

jetbrains ๊ธฐ์ค€ run > edit configuraiton > enviroment variable์„ ํ†ตํ•ด ${}์ฒ˜๋ฆฌํ•˜๋Š” ๊ฑธ ๊ถŒ์žฅ

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${RDS_ENDPOINT}:3306/${RDS_DATABASE}?serverTimezone=Asia/Seoul
    username: admin
    password: ${RDS_PASSWORD}
  security:
    oauth2:
      client:
        registration:
          kakao:
            client-id: ${KAKAO_CLIENT_ID}
            client-name: Kakao
            redirect-uri: http://localhost:8080/login/oauth2/code/kakao
            scope: profile_nickname, profile_image, account_email
            authorization-grant-type: authorization_code
            client-authentication-method: POST
        provider:
          kakao:
            authorization_uri: https://kauth.kakao.com/oauth/authorize
            token_uri: https://kauth.kakao.com/oauth/token
            user-info-uri: https://kapi.kakao.com/v2/user/me
            user_name_attribute: id
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      generate-ddl: true
      format_sql: true
      use_sql_comments: true
  session:
    store-type: jdbc
    jdbc.initialize-schema: always
  jwt:
    secretKey: ${SECRET_KEY}
    blacklist:
      access-token: BlackList_AccessToken_
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  redis:
    host: 127.0.0.1
    port: 6379

springdoc:
  version: 0.0.1
  default-consumes-media-type: application/json # media-type ๋””ํดํŠธ๊ฐ’ ์„ค์ •
  default-produces-media-type: application/json
  swagger-ui:
    path: /swagger-ui.html

app:
  deployment:
    url: ${request_URL}
    processor:
      url: ${redirect_URL}


cloud:
  aws:
    credentials:
      access-key: ${S3_ACCESS_KEY}
      secret-key: ${S3_SECRET_KEY}
      s3:
        bucket: ${S3_BUCKET}
      region:
        static: ap-northeast-2
      stack:
        auto: false

 

 

NGINX ํŒŒ์ผ ์„ค์ •

nginx > nginx.conf

nginx์—์„œ 80๋ฒˆ ํฌํŠธ๋กœ ๋“ค์–ด์˜จ ์š”์ฒญ์„ Spring ์„œ๋ฒ„์˜ 8080๋ฒˆ ํฌํŠธ๋กœ ์—ฐ๊ฒฐ

server {

  listen 80;
  client_max_body_size 0;

  location / {
    proxy_pass http://web:8080;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_redirect off;
  }

}

 

nginx > scripts

#!/bin/bash

# Installing docker engine if not exists
if ! type docker > /dev/null
then
  echo "docker does not exist"
  echo "Start installing docker"
  sudo apt-get update
  sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
  curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
  sudo apt update
  apt-cache policy docker-ce
  sudo apt install -y docker-ce
fi

# Installing docker-compose if not exists
if ! type docker-compose > /dev/null
then
  echo "docker-compose does not exist"
  echo "Start installing docker-compose"
  sudo curl -L "https://github.com/docker/compose/releases/download/1.27.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  sudo chmod +x /usr/local/bin/docker-compose
fi

 

 

Docker ํŒŒ์ผ ์„ค์ •

nginx > Dockerfile

FROM nginx
COPY ./config/nginx/nginx.conf /etc/nginx/conf.d/default.conf

 

docker-compose.yml 

web ์ด๋ฏธ์ง€์™€ nginx ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ›„ ์–ด๋–ป๊ฒŒ ๊ณต์œ ํ• ์ง€ ์„ค์ •

nginx์—์„œ 80๋ฒˆ ํฌํŠธ๋กœ ๋“ค์–ด์˜จ ์š”์ฒญ์„ Spring(web) ์„œ๋ฒ„์˜ 8080๋ฒˆ ํฌํŠธ๋กœ ์—ฐ๊ฒฐ

version: '3'
services:

  web:
    container_name: web
    image: ${{ secrets.DOCKER_REPO }}/docker-web
    expose:
      - "8080"
    ports:
      - "8080:8080"
    environment:
      - TZ=Asia/Seoul

  nginx:
    container_name: nginx
    image: ${{ secrets.DOCKER_REPO }}/docker-nginx
    ports:
      - "80:80"
    depends_on:
      - web
    environment:
      - TZ=Asia/Seoul

 

Dockerfile

FROM openjdk:17
EXPOSE 80
ARG JAR_FILE=/build/libs/docker-0.0.1-SNAPSHOT.jar # ์•„๋ž˜ ์‚ฌ์ง„์—์„œ bootjar ์‹คํ–‰ํ•˜์—ฌ ๊ฒฝ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ
COPY ${JAR_FILE} app.jar # app.jar๋กœ ๋ณต์‚ฌ
ENTRYPOINT ["java","-jar","/app.jar"]

 

 

์ด์ œ ํ”„๋กœ์ ํŠธ๋ฅผ main ๋ธŒ๋žœ์น˜์— push ํ•˜๋ฉด, deploy.yml ํŒŒ์ผ์ด ๋Œ์•„๊ฐ€๋ฉฐ ์ž๋™ ๋ฐฐํฌ (CI/CD) 

 

 

Docker hub์— ์ด๋ฏธ์ง€ Push

๋„์ปคํ—ˆ๋ธŒ์— ์•„๋ž˜์ฒ˜๋Ÿผ ์ด๋ฏธ์ง€๊ฐ€ push๋œ ์ƒํƒœ๋ฅผ ํ™•์ธํ–ˆ๋‹ค๋ฉด,

 

AWS EC2 ์—ฐ๊ฒฐ์—์„œ ์‹คํ–‰ ์ค‘์ธ ๋„์ปค ๋ฐ ๊ฒฝ๋กœ ํ™•์ธ

๋”๋ณด๊ธฐ

docker pull ${{ secrets.DOCKER_REPO }}/docker-web

docker pull ${{ secrets.DOCKER_REPO }}/docker-nginx

docker ps 

web์ด ์•ˆ๋œจ๊ฑฐ๋‚˜ ๋ฐ”๋กœ ์ข…๋ฃŒ๋œ๋‹ค๋ฉด,

๋ฅผ ํ†ตํ•ด 

 

๋”๋ณด๊ธฐ

docker logs {container ID}

๋กœ error ํ™•์ธ -> error๋ฅผ ๊ณ ์นœ ํ›„ ์žฌ๋ฐฐํฌ 

 

 

๋„๋ฉ”์ธ ์—ฐ๊ฒฐ

1. ๊ฐ€๋น„์•„์—์„œ ๋„๋ฉ”์ธ ๊ตฌ์ž… (morak.shop)

2. AWS certification manager์—์„œ SSL ์ธ์ฆ์„œ ๋ฐ›๊ธฐ

3. Route53 ํ˜ธ์ŠคํŒ… ์˜์—ญ ์ƒ์„ฑ 

4. Route53 CNAME ๋ ˆ์ฝ”๋“œ ์ƒ์„ฑ 

5. ๊ฐ€๋น„์•„์— ๋„ค์ž„์„œ๋ฒ„ AWS๋กœ ๋ฐ”๊พธ๊ธฐ 

6. ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ ๋Œ€์ƒ๊ทธ๋ฃน ๋งŒ๋“ค๊ธฐ 

7. ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ ์ƒ์„ฑํ•˜์—ฌ ์•„๊นŒ ๋งŒ๋“  ๋Œ€์ƒ ๊ทธ๋ฃน๊ณผ ์ธ์ฆ์„œ ์ถ”๊ฐ€ 

8. HTTP๋ฅผ HTTPS๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์„ค์ •

9. ' '(๋นˆ์นธ) ์ƒ์„ฑ ํ›„ ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ๋ฅผ Route53์˜ ๋ผ์šฐํŒ…๋Œ€์ƒ์œผ๋กœ ๋“ฑ๋ก

 

 

Swagger๋กœ ์ธํ„ฐ๋„ท์—์„œ ๋ช…์„ธ์„œ ํ™•์ธ

https://morak.shop/swagger-ui/ ์—์„œ ๋ช…์„ธ์„œ ํ™•์ธ ๊ฐ€๋Šฅ

ํ—ค๋”์— JWTํ† ํฐ ๋„ฃ์€ ํ›„, API ํ…Œ์ŠคํŠธ ์ง„ํ–‰

 

 

 


+ ์ถ”๊ฐ€

S3์™€์˜ ์—ฐ๋™

1. ๋ฒ„ํ‚ท ์ƒ์„ฑ: ์—‘์„ธ์Šค ์ฐจ๋‹จ ์„ค์ •์„ ํ•ด์ œ

2. ์‚ฌ์šฉ์ž ์ƒ์„ฑ: AWS console > IAM > ์—‘์„ธ์Šค ๊ด€๋ฆฌ > ์‚ฌ์šฉ์ž > ์‚ฌ์šฉ์ž ์ถ”๊ฐ€ ํด๋ฆญ > ์ง์ ‘ ์ •์ฑ…์—ฐ๊ฒฐ > AmozonS3FullAccess ๋ฅผ ์„ ํƒ > ์‚ฌ์šฉ์ž ์ƒ์„ฑ์„ ํด๋ฆญ
(S3์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” IAM ์‚ฌ์šฉ์ž์—๊ฒŒ S3 ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ฃผ๊ณ , ์—‘์„ธ์Šค ํ‚ค๋ฅผ ๋งŒ๋“ค์–ด ์•ก์„ธ์Šค ํ‚ค๋น„๋ฐ€ ์—‘์„ธ์Šค ํ‚ค๋กœ ์ ‘๊ทผ)

3. ์—‘์„ธ์Šค ํ‚ค ์ƒ์„ฑ: AWS Console > IAM > ์—‘์„ธ์Šค ๊ด€๋ฆฌ์ž > ์‚ฌ์šฉ์ž > ์ƒ์„ฑํ•œ ์‚ฌ์šฉ์ž ์ด๋ฆ„ ํด๋ฆญ > ๋ณด์•ˆ ์ž๊ฒฉ ์ฆ๋ช… > ์—‘์„ธ์Šค ํ‚ค ๋งŒ๋“ค๊ธฐ ํด๋ฆญ -> ์—‘์„ธ์Šค ํ‚ค ์ƒ์„ฑ ์™„๋ฃŒ ํ™”๋ฉด์—์„œ ์ƒ์„ฑ๋œ ๊ณต๊ฐœํ‚ค์™€ ๋น„๋ฐ€ํ‚ค๋ฅผ ํ™•์ธ

4. ์Šคํ”„๋ง ์—ฐ๋™ํ•˜๊ธฐ

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;



@Configuration
public class S3Config {
	@Value("${cloud.aws.credentials.access-key}")
	private String accessKey;
	@Value("${cloud.aws.credentials.secret-key}")
	private String secretKey;
	@Value("${cloud.aws.region.static}")
	private String region;

	@Bean
	public AmazonS3Client amazonS3Client() {
		BasicAWSCredentials awsCredentials= new BasicAWSCredentials(accessKey, secretKey);
		return (AmazonS3Client)AmazonS3ClientBuilder.standard()
			.withRegion(region)
			.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
			.build();
	}
}

 

applicaiton.yml ํŒŒ์ผ์— ์•„๋ž˜ ์„ค์ • ์ถ”๊ฐ€

cloud:
  aws:
    credentials:
      access-key:
      secret-key:
    s3:
      bucket: moraktest
    region:
      static: ap-northeast-2
    stack:
      auto: false

 

 

ํŒŒ์ผ์—…๋กœ๋“œ API ์ƒ์„ฑ

ํ”„๋ก ํŠธ์—์„œ ์ด๋ฏธ์ง€๋ฅผ ํ—ค๋”์™€ ํ•จ๊ป˜ ์—…๋กœ๋“œํ•˜๋ฉด ํ—ค๋”์—์„œ ์นด์นด์˜คID ์ถ”์ถœ

S3์—์„œ ํ•ด๋‹น ์นด์นด์˜คID ๋ช…์œผ๋กœ ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅ

์ด๋ฏธ์ง€ PresignedURL์„ ๋ฐœ๊ธ‰๋ฐ›์•„ RDS์— ์ €์žฅํ•œ ํ›„, ํ”„๋ก ํŠธ๋กœ PresignedURL ์ „๋‹ฌ

   @Operation(summary = "์ž‘์—…๋ฌผ ์ด๋ฏธ์ง€ ๋“ฑ๋ก")
    @PostMapping("/upload")
    public ResponseEntity<List<String>> uploadFiles(HttpServletRequest httpRequest,
                                                    @RequestParam("files") List<MultipartFile> files) {
        try {
            List<String> preSignedUrls = new ArrayList<>();
            User user = jwtTokenProvider.getUserInfoByToken(httpRequest);
            Portfolio portfolio = portfolioRepository.findPortfolioByUser(user);

            for (MultipartFile file : files) {
                // ํŒŒ์ผ์ด ์—†์œผ๋ฉด ์Šคํ‚ต
                if (file == null) {
                    continue;
                }

                String fileName = file.getOriginalFilename();

                // S3 Presigned URL ๋ฐ objectKey ์ƒ์„ฑ
                Map<String, Serializable> presignedUrlInfo = s3service.getPreSignedUrl(httpRequest, fileName);
                String preSignedUrl = removeQueryString(presignedUrlInfo.get("preSignedUrl").toString());
                String objectKey = presignedUrlInfo.get("objectKey").toString();

                ObjectMetadata metadata = new ObjectMetadata();
                metadata.setContentType(file.getContentType());
                metadata.setContentLength(file.getSize());

                // S3์— ํŒŒ์ผ ์—…๋กœ๋“œ
                s3service.uploadFileToS3(objectKey, file, metadata);
                preSignedUrls.add(preSignedUrl);
            }

            // ์—…๋กœ๋“œ๋œ ํŒŒ์ผ์˜ ๊ฐœ์ˆ˜๋งŒํผ Presigned URL์„ ์‚ฌ์šฉํ•˜์—ฌ ํฌํŠธํด๋ฆฌ์˜ค์— ์ด๋ฏธ์ง€ URL์„ ์ €์žฅ
            switch (preSignedUrls.size()) {
                case 4:
                    portfolio.uploadImageUrls(preSignedUrls.get(0), preSignedUrls.get(1), preSignedUrls.get(2), preSignedUrls.get(3));
                    break;
                case 3:
                    portfolio.uploadImageUrls(preSignedUrls.get(0), preSignedUrls.get(1), preSignedUrls.get(2), null);
                    break;
                case 2:
                    portfolio.uploadImageUrls(preSignedUrls.get(0), preSignedUrls.get(1), null, null);
                    break;
                case 1:
                    portfolio.uploadImageUrls(preSignedUrls.get(0), null, null, null);
                    break;
                default:
                    break;
            }

            portfolioRepository.save(portfolio);
            return ResponseEntity.ok(preSignedUrls);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }

 

Presigned Url ๋ฐœ๊ธ‰

 

์นด์นด์˜ค ID ํด๋”์— ์ด๋ฏธ์ง€๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ