ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [AWS] Spring Boot ๋ฐฐํฌ
    ์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ 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 ํด๋”์— ์ด๋ฏธ์ง€๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ

     

     

Designed by Tistory.