모노레포의 강점중 하나는 여러 패키지가 한 레포에 있음에도, 멀티 레포의 배포경험을 포기하지 않는 것이 가능하다는 점이 아닐까 싶다.

Git Webhook 을 CircleCi 에서 전달 받을때, trigging 하는 조건을 Tag 를 사용하도록 하여, 여러 배포 조건( 서비스명, 배포할 환경(dev, prod, staging 등), 프로젝트 버전, Commit Hash 등 )을 모두 만족시킬 수 있도록 했다.

Tag 이름이 너무 길어져 만들기 어렵다? 이 또한 자동화가 가능한 Shell Script 를 작성하여 yarn deploy-dev-admin 같은 간단한 명령어 한번만으로 배포가 가능하도록 했다! (물론, pull request 는 별도로 하게 된다)

배포시작 & 종료 시점에 자동 Slack Message 전송

Untitled

CircleCI Example Script for MonoRepo (config.yml)

// 보안상 실제 사용하는 스크립트에서 일부 제외하고 간략화 했습니다.
// config.yml
version: 2.1

orbs:
  node: circleci/[email protected]
  aws-cli: circleci/[email protected]
  slack: circleci/[email protected]

references:
  context_main: &context
    context:
      - main_context
      - slack-secrets

  executor_node: &executor
    executor:
      name: node/default
      tag: '16.15.0'

  resource_class_medium: &resource_class_medium
    resource_class: medium+

  restore_cache_dev: &restore_cache_dev
    restore_cache:
      key: v1-deps-dev-{{ checksum "yarn.lock" }}
  
  save_cache_dev: &save_cache_dev
    save_cache:
      key: v1-deps-dev-{{ checksum "yarn.lock" }}
      paths: 
        - node_modules

  install: &install
    run:
      name: Authorize NPM & Install Dependency
      command: |
        yarn config set 'npmScopes["SCOPE"].npmAuthToken' $TOKEN
        yarn install --frozen-lockfile

  # must before build
  preset_env_dev: &preset_env_dev
    run:
      name: Set Dev Application Envs
      command: |
        echo 'export REACT_APP_KEY=${REACT_APP_KEY}' >> "$BASH_ENV"
  
  # must before build
  preset_env_prod: &preset_env_prod
    run:
      name: Set Prod Application Envs
      command: |
        echo 'export REACT_APP_KEY=${REACT_APP_PROD_KEY}' >> "$BASH_ENV"

  build: &build
    run:
      name: Build
      command: yarn build-$SERVICE_NAME_PREFIX

  # environments ----------

  common_envs: &common_envs
    environment:
      NODE_OPTIONS: --max_old_space_size=4096
      CI: 'false'

  # Admin Enviroment Variables

  admin_envs: &admin_envs
    run:
      name: Set Admin Envs
      command: |
        echo 'export PROJECT_NAME=react-admin' >> "$BASH_ENV"
        echo 'export SERVICE_NAME_PREFIX=admin' >> "$BASH_ENV"

  # Worker Enviroment Variables

  worker_envs: &worker_envs
    run:
      name: Set Worker Envs
      command: |
        echo 'export PROJECT_NAME=react-worker' >> "$BASH_ENV"
        echo 'export SERVICE_NAME_PREFIX=worker' >> "$BASH_ENV"
  
  # Client Enviroment Variables

  client_envs: &client_envs
    run:
      name: Set Client Envs
      command: |
        echo 'export PROJECT_NAME=react-client' >> "$BASH_ENV"
        echo 'export SERVICE_NAME_PREFIX=client' >> "$BASH_ENV"

  # ---------- environments end

  # aws jobs ----------

  aws_cli_setup: &aws_cli_setup
    aws-cli/setup:
      aws-region: AWS_REGION
      session-duration: '3600'

  aws_s3_sync_dev_burket: &aws_s3_sync_dev_burket
    run:
      name: S3 Sync Dev Bucket
      command: $SYNC_DEV_COMMAND
  
  aws_s3_sync_prod_burket: &aws_s3_sync_prod_burket
    run:
      name: S3 Sync Prod Bucket
      command: $SYNC_PROD_COMMAND

  aws_cloudfront_create_invalidation_dev: &aws_cloudfront_create_invalidation_dev
    run:
      name: Cloudfront create invalidation
      command: $DISTRIBUTION_DEV_COMMAND

  aws_cloudfront_create_invalidation_prod: &aws_cloudfront_create_invalidation_prod
    run:
      name: Cloudfront create invalidation
      command: $DISTRIBUTION_PROD_COMMAND

  # ---------- aws jobs end

  # filters ----------

  admin_dev_tag_filter: &admin_dev_tag_filter
    filters:
      tags:
        only: /^(admin-dev-v)\\d+\\.\\d+\\.\\d+[-a-zA-Z0-9]*/ # ex) admin-dev-v1.255.23-hotfix | admin-dev-v1.2.3
      branches:
        ignore: /.*/

  admin_prod_tag_filter: &admin_prod_tag_filter
    filters:
      tags:
        only: /^(admin-prod-v)\\d+\\.\\d+\\.\\d+[-a-zA-Z0-9]*/ # ex) admin-prod-v1.255.23-hotfix | admin-prod-v1.2.3
      branches:
        ignore: /.*/

  worker_dev_tag_filter: &worker_dev_tag_filter
    filters:
      tags:
        only: /^(worker-dev-v)\\d+\\.\\d+\\.\\d+[-a-zA-Z0-9]*/
      branches:
        ignore: /.*/

  worker_prod_tag_filter: &worker_prod_tag_filter
    filters:
      tags:
        only: /^(worker-prod-v)\\d+\\.\\d+\\.\\d+[-a-zA-Z0-9]*/
      branches:
        ignore: /.*/

  client_dev_tag_filter: &client_dev_tag_filter
    filters:
      tags:
        only: /^(client-dev-v)\\d+\\.\\d+\\.\\d+[-a-zA-Z0-9]*/
      branches:
        ignore: /.*/

  client_prod_tag_filter: &client_prod_tag_filter
    filters:
      tags:
        only: /^(client-prod-v)\\d+\\.\\d+\\.\\d+[-a-zA-Z0-9]*/
      branches:
        ignore: /.*/
  
  # ---------- filters end

  # slack-notify ----------

  slack_notify_start: &slack_notify_start
    slack/notify:
      event: always
      custom: |
        {
          "blocks": [
            {
              "type": "section",
              "text": {
                "text": "[$CIRCLE_PROJECT_REPONAME][$CIRCLE_PROJECT_USERNAME/$CIRCLE_TAG] Deployment Start!"
              }
            }
          ]
        }
  
  slack_notify_pass: &slack_notify_pass
    slack/notify:
      event: pass
      custom: |
        {
          "blocks": [
            {
              "type": "section",
              "text": {
                "text": "[$CIRCLE_PROJECT_REPONAME][$CIRCLE_PROJECT_USERNAME/$CIRCLE_TAG] Deployment Successful! 🎉"
              }
            }
          ]
        }
  
  slack_notify_fail: &slack_notify_fail
    slack/notify:
      event: fail
      custom: |
        {
          "blocks": [
            {
              "type": "section",
              "text": {
                "text": "[$CIRCLE_PROJECT_REPONAME][$CIRCLE_PROJECT_USERNAME/$CIRCLE_TAG] Deployment failed 🚫"
              }
            }
          ]
        }

  # ---------- slack-notify end

jobs:
  admin-dev-deploy:
    <<: *executor
    <<: *resource_class_medium
    <<: *common_envs
    steps:
      - checkout
			- 중략...

  admin-prod-deploy:
    <<: *executor
    <<: *resource_class_medium
    <<: *common_envs
    steps:
      - checkout
	    - 중략...

  worker-dev-deploy:
    <<: *executor
    <<: *resource_class_medium
    <<: *common_envs
    steps:
      - checkout
 			- 중략...

  worker-prod-deploy:
    <<: *executor
    <<: *resource_class_medium
    <<: *common_envs
    steps:
      - checkout
 			- 중략...

  client-dev-deploy:
    <<: *executor
    <<: *resource_class_medium
    <<: *common_envs
    steps:
      - checkout
 			- 중략...

  client-prod-deploy:
    <<: *executor
    <<: *resource_class_medium
    <<: *common_envs
    steps:
      - checkout
 			- 중략...

workflows:
  admin-dev-deploy-workflow:
    jobs:
      - admin-dev-deploy:
          <<: *context
          <<: *admin_dev_tag_filter

  admin-prod-deploy-workflow:
    jobs:
      - admin-prod-deploy:
          <<: *context
          <<: *admin_prod_tag_filter

  worker-dev-deploy-workflow:
    jobs:
      - worker-dev-deploy:
          <<: *context
          <<: *worker_dev_tag_filter

  worker-prod-deploy-workflow:
    jobs:
      - worker-prod-deploy:
          <<: *context
          <<: *worker_prod_tag_filter

  client-dev-deploy-workflow:
    jobs:
      - client-dev-deploy:
          <<: *context
          <<: *client_dev_tag_filter

  client-prod-deploy-workflow:
    jobs:
      - client-prod-deploy:
          <<: *context
          <<: *client_prod_tag_filter