diff --git a/.github/workflows/animation-application-prod-ci-cd-flow.yml b/.github/workflows/animation-application-prod-ci-cd-flow.yml new file mode 100644 index 000000000..7c967e97a --- /dev/null +++ b/.github/workflows/animation-application-prod-ci-cd-flow.yml @@ -0,0 +1,108 @@ +name: animation-prod-application-ci/cd + +on: + push: + branches: + - prod_back_animation + - build/cicd-prod-B-#580 + - 'hotfix/[0-9a-zA-z]+-B-animation-#[0-9a-zA-z]+' + pull_request: + branches: + - prod_back_animation + +jobs: + deploy-animation: + name: animation application deploy + if: ${{ github.event_name == 'push' }} + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ./backend/AnimatedDrawings + + permissions: + id-token: write + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + token: ${{ secrets.CI_PAT }} + + - name: move yaml files + run: | + mkdir -p ./config/yaml + cp -r ../core/src/main/resources/config ./application/config/yaml + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ steps.login-ecr.outputs.registry }}/${{ secrets.AWS_PROD_ECR_ANIMATION_REPOSITORY_NAME }} + tags: | + type=raw,value={{date 'YYYYMMDD-HHmmss'}} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: ./backend/AnimatedDrawings + push: true + tags: ${{ steps.meta.outputs.tags }} + provenance: false + + - name: Get Github action IP + id: ip + uses: haythem/public-ip@v1.3 + + - name: Add Github Actions IP to Security group + run: | + aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_PROD_ANIMATION_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + + - name: Connect ec2 and Run Docker Container + uses: appleboy/ssh-action@v0.1.6 + env: + AWS_ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + with: + host: ${{ secrets.SSH_PROD_ANIMATION_HOST }} + username: ${{ secrets.SSH_USERNAME }} + key: ${{ secrets.SSH_PROD_ANIMATION_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + docker ps -q --filter "name=animated_drawings" | xargs -r docker stop + docker ps -aq --filter "name=animated_drawings" | xargs -r docker rm + aws ecr get-login-password --region ${{ secrets.AWS_REGION }} | docker login --username ${{ secrets.AWS_DOCKER_USER }} --password-stdin ${{ secrets.AWS_USER_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com + docker image prune -f + docker pull ${{ steps.meta.outputs.tags }} + docker run -d -p 5555:5555 -p 9001:9001 --name animated_drawings -v log_volume:/app/application/log -e ENVIRONMENT='dev' ${{ steps.meta.outputs.tags }} + + - name: Remove Github Actions IP from security group + if: always() + run: | + aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_PROD_ANIMATION_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + + - uses: sarisia/actions-status-discord@v1 + if: success() + with: + webhook: ${{ secrets.DISCORD_WEBHOOK }} + status: ${{ job.status }} + content: "여러분 <@384742716933668867> <@1084774841460215839> <@545902166842408960> <@1081452554149449748>\n 배포 완료했습니다!!" + title: "애니메이션 서버 배포 완료 알림" + description: "백엔드 애니메이션 개발 브랜치에 깃허브 액션으로 배포 완료" + image: ${{ secrets.EMBED_IMAGE }} + color: 0x0000ff + url: "https://github.com/tukcomCD2024/DroidBlossom/actions" + username: GitHub Actions Bot + avatar_url: ${{ secrets.AVATAR_URL }} \ No newline at end of file diff --git a/backend/AnimatedDrawings/Dockerfile b/backend/AnimatedDrawings/Dockerfile index 10a575a4a..61cd8c0a1 100644 --- a/backend/AnimatedDrawings/Dockerfile +++ b/backend/AnimatedDrawings/Dockerfile @@ -25,4 +25,6 @@ RUN pip install -e . WORKDIR /app/application +RUN mkdir log + CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/backend/AnimatedDrawings/application/celery_app.py b/backend/AnimatedDrawings/application/celery_app.py index 151a098a9..894a72ef6 100644 --- a/backend/AnimatedDrawings/application/celery_app.py +++ b/backend/AnimatedDrawings/application/celery_app.py @@ -5,12 +5,18 @@ queue_config = QueueConfig() celery = Celery('application.task', - broker=queue_config.get_celery_broker_url(), - include=['application.task.tasks']) + broker=queue_config.get_broker_url(), + include=['application.task.tasks'] + ) celery.conf.result_expires = 300 -celery.conf.task_queues = ( - Queue('task.makeAnimation.queue'), - Queue('task.saveCapsuleSkin.queue'), - Queue('task.sendNotification.queue') -) +celery.conf.task_queues = (Queue('task.makeAnimation.queue'), + Queue('task.saveCapsuleSkin.queue'), + Queue('task.sendNotification.queue') + ) + +if queue_config.PROTOCOL == 'amqps': + import ssl + celery.conf.broker_use_ssl = { + 'cert_reqs': ssl.CERT_REQUIRED + } diff --git a/backend/AnimatedDrawings/application/config/queue_config.py b/backend/AnimatedDrawings/application/config/queue_config.py index 04d70546a..2a0a072f9 100644 --- a/backend/AnimatedDrawings/application/config/queue_config.py +++ b/backend/AnimatedDrawings/application/config/queue_config.py @@ -2,9 +2,11 @@ class QueueConfig: + PROTOCOL = RootConfig.CONFIG_FILE['rabbitmq']['protocol'] USERNAME = RootConfig.CONFIG_FILE['rabbitmq']['username'] BROKER_HOST = RootConfig.CONFIG_FILE['rabbitmq']['host'] PASSWORD = RootConfig.CONFIG_FILE['rabbitmq']['password'] + PORT = RootConfig.CONFIG_FILE['rabbitmq']['port'] VIRTUAL_HOST = RootConfig.CONFIG_FILE['rabbitmq']['virtual-host'] CAPSULE_SKIN_REQUEST_QUEUE_NAME = RootConfig.CONFIG_FILE['rabbitmq'][ 'queue_name'] @@ -16,12 +18,12 @@ class QueueConfig: 'notification_queue_name'] @staticmethod - def get_celery_broker_url() -> str: - return 'pyamqp://%s:%s@%s:5672%s' % (QueueConfig.USERNAME, - QueueConfig.PASSWORD, - QueueConfig.BROKER_HOST, - QueueConfig.VIRTUAL_HOST) - - @staticmethod - def get_kombu_broker_url() -> str: - return f'amqp://{QueueConfig.USERNAME}:{QueueConfig.PASSWORD}@{QueueConfig.BROKER_HOST}/{QueueConfig.VIRTUAL_HOST}' + def get_broker_url() -> str: + return ( + f'{QueueConfig.PROTOCOL}://' + f'{QueueConfig.USERNAME}:' + f'{QueueConfig.PASSWORD}@' + f'{QueueConfig.BROKER_HOST}:' + f'{QueueConfig.PORT}' + f'{QueueConfig.VIRTUAL_HOST}' + ) diff --git a/backend/AnimatedDrawings/application/config/yml b/backend/AnimatedDrawings/application/config/yml index 443596e26..6b0cddbbd 160000 --- a/backend/AnimatedDrawings/application/config/yml +++ b/backend/AnimatedDrawings/application/config/yml @@ -1 +1 @@ -Subproject commit 443596e26746e9818a2ba4a5e8e88056cdbb9e86 +Subproject commit 6b0cddbbd5852fa9f4534ed031102e19349d884b diff --git a/backend/AnimatedDrawings/application/kombu_connection_pool.py b/backend/AnimatedDrawings/application/kombu_connection_pool.py index db57e3727..e0ce840c5 100644 --- a/backend/AnimatedDrawings/application/kombu_connection_pool.py +++ b/backend/AnimatedDrawings/application/kombu_connection_pool.py @@ -18,8 +18,16 @@ def errback(exc, interval): connections = pools.Connections(limit=2) producers = pools.Producers(limit=2) -connection = Connection(hostname=QueueConfig.get_kombu_broker_url(), +if QueueConfig.PROTOCOL == 'amqps': + import ssl + ssl_option = { + 'cert_reqs': ssl.CERT_REQUIRED + } +else: + ssl_option = None +connection = Connection(hostname=QueueConfig.get_broker_url(), connect_timeout=7, + ssl=ssl_option, errback=errback) logger.info('레빗 엠큐 커넥션 풀 연결 설정 완료') diff --git a/backend/AnimatedDrawings/application/logging/logger_factory.py b/backend/AnimatedDrawings/application/logging/logger_factory.py index b0f1f4f3c..4de066433 100644 --- a/backend/AnimatedDrawings/application/logging/logger_factory.py +++ b/backend/AnimatedDrawings/application/logging/logger_factory.py @@ -18,13 +18,13 @@ def get_logger(name: str) -> logging.Logger: LoggerFactory._setup_handler(LoggerConfig.FORMAT_STRING, LoggerConfig.LOGGING_LEVEL, - logger, - LoggerConfig.APPLICATION_OUTPUT_FILE_PATH) + logger + ) return logger @staticmethod - def setup_logger(logger: logging.Logger, output_file_path: str) -> None: + def setup_logger(logger: logging.Logger) -> None: """ 파라미터로 받은 로거에 포맷터를 설정한다. :param logger: 설정할 로거 @@ -32,20 +32,15 @@ def setup_logger(logger: logging.Logger, output_file_path: str) -> None: """ LoggerFactory._setup_handler(LoggerConfig.FORMAT_STRING, LoggerConfig.LOGGING_LEVEL, - logger, - output_file_path) + logger + ) @staticmethod - def _setup_handler(format_string, level, logger, output_file_path): + def _setup_handler(format_string, level, logger): formatter = logging.Formatter(format_string) stream_handler = logging.StreamHandler(sys.stdout) stream_handler.setLevel(level) stream_handler.setFormatter(formatter) - file_handler = logging.FileHandler(filename=output_file_path) - file_handler.setLevel(level) - file_handler.setFormatter(formatter) - - logger.addHandler(file_handler) logger.addHandler(stream_handler) diff --git a/backend/AnimatedDrawings/application/task/base_task.py b/backend/AnimatedDrawings/application/task/base_task.py index 22595acfe..ab9f5487c 100644 --- a/backend/AnimatedDrawings/application/task/base_task.py +++ b/backend/AnimatedDrawings/application/task/base_task.py @@ -5,7 +5,6 @@ from celery.utils.log import get_task_logger from kombu import Queue, Exchange -from application.config.logger_config import LoggerConfig from application.config.queue_config import QueueConfig from application.kombu_connection_pool import producers, connection from application.logging.logger_factory import LoggerFactory @@ -24,7 +23,7 @@ def __init__(self): @celery.signals.after_setup_task_logger.connect def on_after_setup_logger(logger, **kwargs): - LoggerFactory.setup_logger(logger, LoggerConfig.CELERY_OUTPUT_FILE_PATH) + LoggerFactory.setup_logger(logger) def before_start(self, task_id, args, kwargs): self.task_logger.debug(kwargs) diff --git a/backend/AnimatedDrawings/supervisord.conf b/backend/AnimatedDrawings/supervisord.conf index f4bdfd4a2..399d395f9 100644 --- a/backend/AnimatedDrawings/supervisord.conf +++ b/backend/AnimatedDrawings/supervisord.conf @@ -12,16 +12,24 @@ directory = /app/application command = /opt/conda/envs/animated_drawings/bin/celery -A celery_app flower --conf="/app/application/config/flowerconfig.py" priority = 100 loglevel=info -stdout_logfile = /var/log/flower.log -stderr_logfile = /var/log/flower.err +stdout_logfile = /app/application/log/flower.log +stderr_logfile = /app/application/log/flower.err +stdout_logfile_maxbytes = 50MB +stdout_logfile_backups = 30 +stderr_logfile_maxbytes = 50MB +stderr_logfile_backups = 30 [program:application] directory = /app/application command = /opt/conda/envs/animated_drawings/bin/python3 -u animation_queue.py loglevel=info priority = 100 -stdout_logfile = /var/log/application.log -stderr_logfile = /var/log/application.err +stdout_logfile = /app/application/log/application.log +stderr_logfile = /app/application/log/application.err +stdout_logfile_maxbytes = 50MB +stdout_logfile_backups = 30 +stderr_logfile_maxbytes = 50MB +stderr_logfile_backups = 30 environment=ENVIRONMENT=%(ENV_ENVIRONMENT)s,PYOPENGL_PLATFORM="osmesa" [program:celery] @@ -29,7 +37,11 @@ directory = /app/application command = /opt/conda/envs/animated_drawings/bin/celery -A celery_app worker priority = 200 loglevel=info -stdout_logfile = /var/log/celeryd.log -stderr_logfile = /var/log/celeryd.err +stdout_logfile = /app/application/log/celeryd.log +stderr_logfile = /app/application/log/celeryd.err +stdout_logfile_maxbytes = 50MB +stdout_logfile_backups = 30 +stderr_logfile_maxbytes = 50MB +stderr_logfile_backups = 30 environment=ENVIRONMENT=%(ENV_ENVIRONMENT)s,PYOPENGL_PLATFORM="osmesa"