diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml new file mode 100644 index 0000000..dacba83 --- /dev/null +++ b/.github/workflows/cd-prod.yml @@ -0,0 +1,147 @@ +name: CD-PROD + +on: + workflow_run: + workflows: [ "CI-PROD" ] + types: [ completed ] + workflow_dispatch: + +concurrency: + group: cd-prod + cancel-in-progress: false + +permissions: + contents: read + id-token: write + +env: + AWS_REGION: ${{ vars.AWS_REGION }} + ECR_REGISTRY: ${{ vars.ECR_REGISTRY }} + ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }} + ECS_CLUSTER: ${{ vars.ECS_CLUSTER }} + ECS_SERVICE: ${{ vars.ECS_SERVICE }} + TASK_FAMILY: ${{ vars.TASK_FAMILY }} + CONTAINER_NAME: ${{ vars.CONTAINER_NAME }} + +jobs: + deploy: + if: > + github.event_name == 'workflow_dispatch' || + ( + github.event_name == 'workflow_run' && + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'main' + ) + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Configure AWS credentials (OIDC) + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + aws-region: ${{ env.AWS_REGION }} + + - name: Resolve image tag + id: tag + run: | + if [ "${{ github.event_name }}" = "workflow_run" ]; then + IMAGE_TAG="${{ github.event.workflow_run.head_sha }}" + else + IMAGE_TAG="${{ github.sha }}" + fi + IMAGE_TAG="${IMAGE_TAG:0:7}" + echo "image_tag=${IMAGE_TAG}" >> "$GITHUB_OUTPUT" + + - name: Verify image exists in ECR + run: | + aws ecr describe-images \ + --repository-name "${ECR_REPOSITORY}" \ + --image-ids imageTag="${{ steps.tag.outputs.image_tag }}" \ + > /dev/null + + - name: Get current task definition + run: | + aws ecs describe-task-definition \ + --task-definition "${TASK_FAMILY}" \ + --query 'taskDefinition' > taskdef.json + + - name: Render new task definition + env: + IMAGE_URI: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ steps.tag.outputs.image_tag }} + run: | + jq \ + --arg CN "${CONTAINER_NAME}" \ + --arg IMG "${IMAGE_URI}" \ + --arg SPRING_PROFILES_ACTIVE "prod" \ + --arg SERVER_PORT "${{ vars.SERVER_PORT }}" \ + --arg MONGO_HOST "${{ vars.MONGO_HOST }}" \ + --arg MONGO_PORT "${{ vars.MONGO_PORT }}" \ + --arg MONGO_DB "${{ vars.MONGO_DB }}" \ + --arg REDIS_HOST "${{ vars.REDIS_HOST }}" \ + --arg REDIS_PORT "${{ vars.REDIS_PORT }}" \ + --arg KAFKA_BROKERS "${{ vars.KAFKA_BROKERS }}" \ + --arg EUREKA_ENABLED "${{ vars.EUREKA_ENABLED }}" \ + --arg EUREKA_HOST "${{ vars.EUREKA_HOST }}" \ + --arg EUREKA_PORT "${{ vars.EUREKA_PORT }}" \ + --arg JWT_SECRET "${{ secrets.JWT_SECRET }}" \ + --arg INTERNAL_AUTH_SECRET "${{ secrets.INTERNAL_AUTH_SECRET }}" \ + --arg PERSONAL_INFO_SECRET "${{ secrets.PERSONAL_INFO_SECRET }}" \ + --arg JWT_ACCESS_EXPIRE "${{ vars.JWT_ACCESS_TOKEN_EXPIRATION_SECONDS }}" \ + --arg JWT_REFRESH_EXPIRE "${{ vars.JWT_REFRESH_TOKEN_EXPIRATION_SECONDS }}" \ + ' + .containerDefinitions |= map( + if .name == $CN then + .image = $IMG + | .environment = [ + {"name":"SPRING_PROFILES_ACTIVE","value":$SPRING_PROFILES_ACTIVE}, + {"name":"SERVER_PORT","value":$SERVER_PORT}, + {"name":"MONGO_HOST","value":$MONGO_HOST}, + {"name":"MONGO_PORT","value":$MONGO_PORT}, + {"name":"MONGO_DB","value":$MONGO_DB}, + {"name":"REDIS_HOST","value":$REDIS_HOST}, + {"name":"REDIS_PORT","value":$REDIS_PORT}, + {"name":"KAFKA_BROKERS","value":$KAFKA_BROKERS}, + {"name":"EUREKA_ENABLED","value":$EUREKA_ENABLED}, + {"name":"EUREKA_HOST","value":$EUREKA_HOST}, + {"name":"EUREKA_PORT","value":$EUREKA_PORT}, + {"name":"JWT_SECRET","value":$JWT_SECRET}, + {"name":"INTERNAL_AUTH_SECRET","value":$INTERNAL_AUTH_SECRET}, + {"name":"PERSONAL_INFO_SECRET","value":$PERSONAL_INFO_SECRET}, + {"name":"JWT_ACCESS_TOKEN_EXPIRATION_SECONDS","value":$JWT_ACCESS_EXPIRE}, + {"name":"JWT_REFRESH_TOKEN_EXPIRATION_SECONDS","value":$JWT_REFRESH_EXPIRE}, + {"name":"TZ","value":"Asia/Seoul"} + ] + else . + end + ) + | del( + .taskDefinitionArn, .revision, .status, + .requiresAttributes, .compatibilities, + .registeredAt, .registeredBy + ) + ' taskdef.json > taskdef.new.json + + - name: Register new task definition revision + id: register + run: | + ARN=$(aws ecs register-task-definition \ + --cli-input-json file://taskdef.new.json \ + --query 'taskDefinition.taskDefinitionArn' \ + --output text) + echo "task_def_arn=${ARN}" >> "$GITHUB_OUTPUT" + + - name: Deploy to ECS service + run: | + aws ecs update-service \ + --cluster "${ECS_CLUSTER}" \ + --service "${ECS_SERVICE}" \ + --task-definition "${{ steps.register.outputs.task_def_arn }}" \ + --force-new-deployment + + - name: Wait for stable + run: | + aws ecs wait services-stable \ + --cluster "${ECS_CLUSTER}" \ + --services "${ECS_SERVICE}" diff --git a/.github/workflows/ci-prod.yml b/.github/workflows/ci-prod.yml index 7060530..0c784b2 100644 --- a/.github/workflows/ci-prod.yml +++ b/.github/workflows/ci-prod.yml @@ -4,7 +4,8 @@ on: pull_request: branches: [ main, dev ] push: - branches: [ main ] + branches: [ main, dev ] + permissions: contents: read # 내 코드를 읽을 수 있게(코드이동) 접근 권한을 준다. diff --git a/src/main/java/com/michelet/catalog/infrastructure/config/SecurityConfig.java b/src/main/java/com/michelet/catalog/infrastructure/config/SecurityConfig.java index 1287e85..889d3e5 100644 --- a/src/main/java/com/michelet/catalog/infrastructure/config/SecurityConfig.java +++ b/src/main/java/com/michelet/catalog/infrastructure/config/SecurityConfig.java @@ -41,6 +41,7 @@ protected boolean shouldNotFilter(HttpServletRequest request) { .formLogin(AbstractHttpConfigurer::disable) .httpBasic(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth + .requestMatchers("/actuator/health", "/actuator/prometheus").permitAll() .requestMatchers("/internal/**").permitAll() .requestMatchers("/api/v1/**").permitAll() .anyRequest().authenticated()