Networks/Project

SK networks AI Camp - toyproject AWS 및 Github 트러블 이슈

코딩하는 Español되기 2024. 10. 17. 18:00

토이 프로젝트 주제 변경으로 인해 롤 프로젝트가 중지되고 새로운 아이디어로 진행했습니다.

 

진행하는 동안에 백엔드와 AWS의 CICD를 담당하였는데 진행하는 동안 겪은 트러블을 적어보고자 합니다.

(몇일을 에러를 붙잡고 AWS를 진행하면서 공부를 많이 한 것 같습니다)

 

크게 보면 에러가 3개가 있었습니다.

 

1. Github 보안 관련 문제

2. Service 배포하는 과정에서 발생한 문제(이 문제에서 몇일을 잡고 있었습니다.)

3. 배포 후 Swagger에 접속하여 확인한 결과 발생한 에러

 

[1. 보안 문제]

이 문제의 경우 토이프로젝트를 하면서 겪었는 점이 오히려 다행인 것 같습니다.

회사 들어가서 이랬으면 바로 짤렸을텐데... 헤헤

ECS, AW ID, ECR Repository 및 URI, 등등 보안을 신경 써야하는 부분을 그대로 Git에 올려버렸습니다.

 

Commit을 한 기록에 남아있었기에 로그를 초기화 하기 위하여 브랜치를 삭제하는 과정을 진행했습니다.

 

1. main 브랜치를 복제

- git orphan 브랜치명 :  하나의 git repository에서 다른 브랜치나 커밋으로부터 단절된 새로운 history를 가지는 브랜치를 생성

- commit 만 적고 enter를 한 후 i 를 누르고 커밋 내용 입력 후 esc하고 :wq를 하여 작성했습니다.

git checkout --orphan last_branch
git add -A
git commit

 

2. 기존 main 브랜치 삭제 후 orphan 브랜치를 main으로 변경

- git branch -D 브랜치명 : 브랜치 삭제

- git branch -m 브랜치명 : 현재 브랜치의 브랜치명을 변경

git branch -D main
git branch -m main
git push -f origin main

 

해당 과정을 진행하여 commit했던 로그를 모두 삭제하였습니다...

또한 나머지 보안에 신경써야 하는 건 AWS에서 환경변수로 등록하여 진행했습니다.

 

2. AWS Service 관련 이슈

저희 아키텍처의 경우 아래와 같습니다. 해결한 코드의 경우 아키텍쳐 밑에 먼저 적어 두었습니다.

더보기

파일 경로

 

 

Dockerfile

FROM gradle:7.6-jdk17-alpine as build

ENV APP_HOME=/apps

WORKDIR $APP_HOME

COPY ./build.gradle ./settings.gradle ./gradlew $APP_HOME

COPY ./gradle $APP_HOME/gradle

RUN chmod +x gradlew

RUN ./gradlew build || return 0

COPY ./src $APP_HOME/src

RUN ./gradlew clean build

FROM openjdk:17-jdk-alpine

ENV APP_HOME=/apps
ARG ARTIFACT_NAME=app.jar
ARG JAR_FILE_PATH=build/libs/[프로젝트명]-0.0.1-SNAPSHOT.jar


WORKDIR $APP_HOME

COPY --from=build $APP_HOME/$JAR_FILE_PATH $ARTIFACT_NAME

# 컨테이너 헬스 체크: 8080 포트의 /healthcheck 확인
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -f http://localhost:8080/healthcheck || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]

 

application.yml

spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: ${SPRING_DATASOURCE_URL}
    username: ${SPRING_DATASOURCE_USERNAME}
    password: ${SPRING_DATASOURCE_PASSWORD}

  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: true
    database-platform: org.hibernate.dialect.PostgreSQLDialect

  # 로깅 설정
  logging:
    level:
      root: INFO
      org.springframework.web: DEBUG
      com.toyproject.jobadream: DEBUG
    pattern:
      console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"

springdoc:
  swagger-ui:
    urls:
      - name: Jobadream API
        url: /v3/api-docs
    server:
      url: ${SERVER_URL}

 

buildspec.yml

 원래 env: 안에 AWS_DEFAULT_REGION 등을 적어줘야하지만 Codepipeline, ECS TaskDefinition의 환경변수로 등록하여 Git에 올렸습니다.

version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - 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

  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -f ./Dockerfile -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $REPOSITORY_URI

  post_build:
    commands:
      - echo package Source...
      - echo push the Docker image...
      - docker push $REPOSITORY_URI

      # Give your container name
      - printf '[{"name":"%s","imageUri":"%s"}]' $ECS_CONTAINER_NAME $REPOSITORY_URI > imagedefinitions.json
      - echo $ECS_CONTAINER_NAME
      - echo printing imagedefinitions.json
      - cat imagedefinitions.json

artifacts:
  files:
    - imagedefinitions.json

 

build.gradle

 

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.4'
    id 'io.spring.dependency-management' version '1.1.6'
    id 'org.asciidoctor.jvm.convert' version '3.3.2'
}

group = '' // 그룹명 입력
version = '' //버전 입력

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    mavenCentral()
}

ext {
    set('snippetsDir', file("build/generated-snippets"))
}

dependencies {
    // Web
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.session:spring-session-core'
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '3.3.4'

    // Tool
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'

    // Data Base
    runtimeOnly 'org.postgresql:postgresql'

    // Test
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // API
    implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.6.0'
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

    // Etc
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

// JAR 설정 추가
jar {
    enabled = true // JAR 파일 생성 활성화
    archiveBaseName = '' // 생성될 JAR 파일의 기본 이름
    archiveVersion = '0.0.1-SNAPSHOT' // 버전 설정
    manifest {
        attributes(
                'Main-Class': '' // SpringApplication 시작 경로
        )
    }
}

 

[배포과정]

1. ECR에 이미지를 업로드

2. Codepipeline을 구성하여 Github에 푸시 요청이 있을 경우 다시 진행하도록 트리거를 설정

3. ECS에서 Service를 배포

4. LoadBalancer를 통하여 트래픽이 초과할 경우 Task를 자동으로 나누어 주는 방법

 

이 과정으로 진행하는데 ECS에서 배포하는 과정에서 오류는 없지만 unhealth하다면서 draining되었습니다.

그래서 아래의 방법으로 시도 해보았습니다.

방법 1) 로드밸런스 없이 배포

    - 1. 숨김 없이 모든 파일을 기록(비밀번호, 유저 아이디 등)한 파일을 ECS에 업로드
    - 2. 환경 변수 설정하여 서비스 배포하여 ECS에서 Service 배포

        ● ${변수명} 으로 ECS에서 환경변수로 설정하여 값을 받아오는 방식으로 진행


    3. 로드밸런스 없이 네트워킹 default 로만 돌려보기


    4. 로드밸런서 추가(이전에 생성한 로드밸런서, 리스너, 대상그룹을 사용)

    로드밸런서 부분을 제가 하지 않았기에 더 공부해봐야 할 것 같습니다.

계속 ECS가 unhealth하여 드레이닝되는 현상으로 아래의 방법을 시도해보았습니다.

1. nginx도 추가하여 80포트로 nginx를 열어 8080에 Spring Tomcat서버로 보내는 방법을 사용(실패)

nginx.conf 파일은 삭제하여 어떻게 적었는 지 기억이 안납니다..

2. 헬스체크가 없는 줄 알고 healthcheck 하는 컨트롤러 제작 후 변경 -> 실패(똑같이 draining)

3. LoadBalancer의 상태 체크의 반환 값을 404로 변경 -> 성공

참고한 링크 포스팅에서 https://programmer-eun.tistory.com/137

 

"스프링 부트에서 아무런 구현도 하지 않고 초기 상태 그대로 배포해서 접속 시 404로 응답되었기 때문이에요. 저처럼 아무 구현없이 바로 배포하실 경우, 고급 상태 검사 설정의 성공 코드를 404로 변경"이라고 되어있었습니다.

 

그래서 상태 검사하는 코드를 404로 바꾸어 주었는데 성공했습니다.

단순히 상태 검사만 안되는 상황이었기에(로드밸런스 안 쓰고 ECS 배포 성공 및 로컬에서 성공) 배포에 성공할 수 있었습니다.

 

AWS에서 애를 많이 먹었습니다. 하지만 이 과정에서 그래도 스트레스 많이 받았지만 많이 공부할 수 있었습니다...

 

3. 배포 완료 후에 Swagger가 http 요청을 보내서 생긴 오류

이 문제는 그래도 가장 애를 먹었던 배포를 끝냈기에 가벼운 마음으로 해결했습니다.

도메인에서 api연결이 된 것을 확인하고 swagger를 들어갔지만 아래와 같이 나왔습니다.

도메인은 https이지만 Swagger에서 연결된 Servers는 http였습니다.

그래서 개발자 도구를 통해 네트워크 송수신을 확인하니 아래의 에러가 발생했습니다.

기본적으로 swagger는 http로 요청을 보내어, https로 요청을 보내기 위해서는 추가적인 작업이 필요하기에

SpringBoot를 실행하는 파일에 아래의 코드를 더해주어 해결했습니다.(어노테이션 추가)

[에러 메시지]

swagger-ui-bundle.js:2 Mixed Content: The page at 'api 요청한 주소' was loaded over HTTPS, but requested an insecure resource "주소" This request has been blocked; the content must be served over HTTPS.

package com.toyproject.jobadream;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.servers.Server;

// 추가해준 어노테이션(@OpenAPIDefinition)
@OpenAPIDefinition(servers = {@Server(url = "/", description = "Default Server URL")})
@SpringBootApplication
public class JobadreamApplication {

	public static void main(String[] args) {
		SpringApplication.run(JobadreamApplication.class, args);
	}
}

 

아래의 과정을 통해 aws에서 Codepipeline을 생성하고 Service 배포에 성공하였습니다.

 

코드에 관한 리뷰는 이 후 진행하겠습니다.