AWS EC2 CI/CD 배포 삽질 과정 - Secret 처리와 HTTPS 통신

2023. 9. 5. 15:45Cloud/AWS

 

1. application.yml 어떻게 처리할까..

AWS 설정들을 완료했고 jar파일을 정상적으로 EC2에 저장하는데 성공했지만

문제가 됐던 부분은 어플리케이션을 구동하기 위한 민감정보(Credential)였다.

 

application.yml 혹은 application.properties 혹은 application.xml 등에

소셜 로그인 키값, 데이터베이스 주소-아이디-비밀번호 등 개인정보가 너무나 많았다.

 

Git Action을 이용해서 S3에 올릴 때는 AWS IAM Access Key와 Secret Access Key를

Git Action Secret에다가 저장해놓고 Git Action Workflow를 이용하여 올렸었다.

      # S3 Bucket으로 copy
      - name: Deliver to AWS S3
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          aws s3 cp \
          --region ap-northeast-2 \
          --acl private \
          ./springboot-intro-build.zip s3://${{ secrets.AWS_S3_BUCKET_NAME }}/
 

하지만 .github > workflow > gradle.yml (Git Action CI/CD) 파일에서는 Secret키를 해석할 수 있지만
application.yml파일에서 ${{}} 이런식으로 secret을 써봤자 "${{}}" 문자열 그대로 들어가서 키값이 제대로 들어가지 않았다.

그래서 찾은 방법이 Git Action CI/CD 파일에서는 이 secret키값을 읽을 수 있으니까

이 파일 안에서 S3에 올리기 전에 application.yml파일을 만드는 명령을 짜서 S3에 넣어주는 방법을 선택 했다.


1. Github Secret & Git Action 통해 application.yml 직접 생성

디렉토리 생성 > 디렉토리 이동 > (application.properties) 파일 생성 > echo함수를 이용해서 파일 안에 시크릿 키 입력
여기서 yml이 아니라 properties파일을 선택한 건 Git Action Secret키를 Github에 입력할 때
secret_key=value 이런식으로 입력하기 편해서 선택했다.ㅎ_ㅎ

jobs:
  build:
    # 실행 환경 지정
    runs-on: ubuntu-latest

    # Task의 sequence를 명시한다.
    steps:
      - uses: actions/checkout@v3

      # 환경변수 파일 생성
      - name: Generate Environment Variable File
        run: |
          echo "$ES_URL\n$ES_CLUSTER_NAME" >> application.properties
          echo "$SCHEDULER_STAR_AVG" >> application.properties
          echo "$SCHEDULER_NOTIFICATION" >> application.properties
          echo "$SERVER_PORT" >> application.properties
          echo "$DB_URL\n$DB_USERNAME\n$DB_PWD" >> application.properties
          echo "$REDIS_HOST\n$REDIS_PORT" >> application.properties
          echo "$JWT_SECRET" >> application.properties
          echo "$MAIL_HOST\n$MAIL_PORT\n$MAIL_USERNAME\n$MAIL_PWD" >> application.properties
          echo "$GOOGLE_CLIENT_ID\n$GOOGLE_CLIENT_SECRET" >> application.properties
          echo "$NAVER_CLIENT_ID\n$NAVER_CLIENT_SECRET\n$NAVER_REDIRECT_URI" >> application.properties
          echo "$KAKAO_CLIENT_ID\n$KAKAO_CLIENT_SECRET\n$KAKAO_REDIRECT_URI" >> application.properties
          echo "$TARGET_URL_SUCCESS\n$TARGET_URL_FAIL" >> application.properties

        env:
          ES_URL: ${{ secrets.ES_URL }}
          ES_CLUSTER_NAME: ${{ secrets.ES_CLUSTER_NAME }}
          SCHEDULER_STAR_AVG: ${{ secrets.SCHEDULER_STAR_AVG }}
          SCHEDULER_NOTIFICATION: ${{ secrets.SCHEDULER_NOTIFICATION }}
          SERVER_PORT: ${{ secrets.SERVER_PORT }}
          DB_URL: ${{ secrets.DB_URL }}
          DB_USERNAME: ${{ secrets.DB_USERNAME }}
          DB_PWD: ${{ secrets.DB_PWD }}
          REDIS_HOST: ${{ secrets.REDIS_HOST }}
          REDIS_PORT: ${{ secrets.REDIS_PORT }}
          JWT_SECRET: ${{ secrets.JWT_SECRET }}
          MAIL_HOST: ${{ secrets.MAIL_HOST }}
          MAIL_PORT: ${{ secrets.MAIL_PORT }}
          MAIL_USERNAME: ${{ secrets.MAIL_USERNAME }}
          MAIL_PWD: ${{ secrets.MAIL_PWD }}
          GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
          GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
          NAVER_CLIENT_ID: ${{ secrets.NAVER_CLIENT_ID }}
          NAVER_CLIENT_SECRET: ${{ secrets.NAVER_CLIENT_SECRET }}
          NAVER_REDIRECT_URI: ${{ secrets.NAVER_REDIRECT_URI }}
          KAKAO_CLIENT_ID: ${{ secrets.KAKAO_CLIENT_ID }}
          KAKAO_CLIENT_SECRET: ${{ secrets.KAKAO_CLIENT_SECRET }}
          KAKAO_REDIRECT_URI: ${{ secrets.KAKAO_REDIRECT_URI }}
          TARGET_URL_SUCCESS: ${{ secrets.TARGET_URL_SUCCESS }}
          TARGET_URL_FAIL: ${{ secrets.TARGET_URL_FAIL }}
 

짜잔! 완성!,,,,,, 이라고 하기엔 어딘가 이상해보인다.
다른 프로젝트들을 아무리 참고해도 Git Action파일에서 이렇게 긴 명령은 없었다...


2. application.properties 텍스트를 통으로 Secret 관리

application.properties 파일로 바꿔서

key1=value1

key2=value2

를 몽땅 하나의 Secret으로 바꿔서 처리한 뒤에

워크플로우를 아래와 같이 한줄에 구성했다.

jobs:
  build:
    # 실행 환경 지정
    runs-on: ubuntu-latest

    # Task의 sequence를 명시한다.
    steps:
      - uses: actions/checkout@v3

      # 환경변수 파일 생성
      - name: Generate Environment Variable File
        run: |
          mkdir ./src/main/resources
          touch ./src/main/resources/application.properties
          echo "${{ secrets.APPLICATION_PROPERTIES }}" > ./src/main/resources/application.properties
          cat ./src/main/resources/application.properties

2. HTTPS 통신

 

CloudFront를 통해서 받아온 정적파일을 통해
백엔드 서버로 요청을 보낼 때 브라우저 자체에서 HTTPS로 요청을 하게 되면서 EC2에서 프로토콜 오류가 발생했다.

EC2에 프론트를 올려볼까도 고민해봤지만

  1. 남은 EC2 프리티어 시간이 얼마 없었다. - 프로젝트 시작때 EC2를 이미 띄워둔 상태였기 때문에,,,
  2. 프론트의 인력난 문제로,,, (백엔드3, 프론트2로 기능개발 및 버그 고치는 데 시간을 쏟고 계셨다...)

어쩔수 없이 백엔드 서버가 배포된 EC2에서 해결해야만 했다.
EC2에서 HTTPS 통신을 받는 방법


1. HTTPS -> HTTP 리다이렉트

EC2에 로드밸런서를 연결해서
로드밸런서로 HTTPS 통신을 받고, 로드밸런서에서
EC2 서버에 HTTP 통신으로 리다이렉트 시켜주는 방법이다.

하지만 HTTPS는 말그대로 Secure Layer가 추가되는 것이기 때문에 신뢰성을 보장하는 인증서가 필요했다.
바로, SSL/TLS 인증서였다.
다행히 AWS에는 ACM(Amazon Certificate Manager)가 있어서 인증서 발급을 정말 간단하게 할 수 있었다.


2. SSL/TLS 인증서 발급

ACM을 이용해서 인증서를 발급받기 위해서는 도메인주소가 필요했다.

신규 회원 이벤트로 도메인을 싸게 구매할 수 있는 사이트에서 550원/1년 정도에 도메인을 구입하였다.

자 이제 인증서 받자! 도메인 입력하고! 요청! 여기까지 정말 나는 너무나 멍청했다는걸 다시금 깨달았다.

도메인만 사면 뭐하니,, 도메인은 그냥 이름이고 아무것도 연결된게 없는데,,,

 

ACM에서 도메인을 검증하기 위해서 요청을 보내고 난 뒤에 인증서를 내주는 것 같았다.

그럼 결국에는 내 도메인이랑 EC2서버랑도 연결해주어야 되는 것이었다.

점점 산으로 간다,,, 탈모량 20% 증가,,,,


3. 내가 산 도메인과 EC2를 연결

다행히 Route53을 이용해서 도메인과 Route53을 연결하고 도메인으로 오는 요청을 라우팅 시켜줄 수 있었다.

다만, Route 53은 인스턴스(?) 개수마다 1달에 0.5달러 정도 부과된다.

EC2 초과비용은 예상하기 힘들지만 1달에 0.5달러는 충분히 감당 가능한 수준이라고 생각되어 진행했다.

또한, Route53으로 도메인과 EC2를 연결하는 방법은 레퍼런스가 많아서 쉽게 진행할 수 있었다.

다만, 생각보다 그 과정이 꽤 많기 때문에 공식문서나 정리가 잘 되어있는 블로그를 보고 차례차례 잘 따라해야 한다.

 

자 이제 내가 산 도메인 www.머시기.shop 이 도메인으로 ACM에서 요청을 보내면 EC2에서 응답하고

ACM에서 검증이 완료되고 SSL/TLS 인증서를 발급해줄 것이다!

 

그런데, 이 과정에서 시간이 꽤 많이 걸렸다. 왜냐하면, 바로 이전 과정에서 도메인이 DNS 서버에 퍼지기까지 생각보다 오래걸렸기 때문이디. 이 부분을 원래부터 인지한 것은 아니고 공식문서에 하도 등록이 안된다고 문의가 많이 왔는지 아예 공지하고 있었다.

 

아무튼 최대 2일이라니,,, 일단 최대한 빨리 되기를 바라면서 한없이 기다렸고, 정상적으로 발급되는 것을 확인했다.


4. 도메인 -> Route 53 -> 로드밸런서 -> EC2

Route 53에서 등록된 도메인을 로드밸런서로 라우팅 시켜주는 과정

이제 모든 설정이 다 끝났다.

하지만, 지금은 등록된 도메인으로 요청을 날리게 되면 EC2로 직빵으로 들어가서

똑같은 오류가 발생하기 때문에

도메인을 로드밸런서로 연결시켜주는 라우팅 과정이 추가적으로 필요했다.

(이 부분도 레퍼런스가 생각보다 많이 있어서 금방 찾을 수 있다.)

이제 위 그림처럼 CloudFront에서 도메인으로 요청이 들어오면

Route 53을 거쳐서

HTTPS 요청이 EC2의 로드밸런서로 들어가고

로드밸런서에서 HTTP로 리다이렉트시켜

EC2로 들어가게 되면서 요청이 성공적으로 들어가게 된다.

 

정리하자면,

로드밸런서로 HTTPS 요청을 HTTP로 리다이렉트 시키기 위해서

SSL/TLS 인증서를 발급받기 위해서

도메인을 구입하였고,

 

도메인을 Route 53에서 EC2로 연결해주고

SSL/TLS 인증서를 발급받았고, 이를 로드밸런서에 등록해주었다.

 

그리고 도메인을 Route 53에서 로드밸런서와 연결해주어

최종적으로 아래 절차를 거쳐서 HTTPS 통신이 가능하게 했다.

HTTPS -> 도메인 -> 도메인 서버 -> Route 53 -> EC2 LoadBalancer -> HTTP -> EC2

 

결론!

아마존에서 살아남기 위해서는 무과금으로는 너무 힘들다.

현질이 필요한 시점이다..

'Cloud > AWS' 카테고리의 다른 글

[AWS SAA C03] 2023 AWS SAA 합격 후기  (1) 2023.09.06