propose-translation-update.sh 실행을 위한 Zuul CI/CD 파이프라인 흐름 분석

OpenStack i18n에서 번역 파일을 자동으로 업데이트하는 propose-translation-update.sh 스크립트는 Zuul CI/CD 파이프라인을 거쳐 실행된다.

이 스크립트가 실제로 동작하기까지는 스케줄링, 프로젝트 스캔, Job 생성, 실행 환경 준비 등 여러 단계를 거치는데, 전체 프로세스는 완전 자동화되어 있어 개발자나 번역자의 수동 개입 없이도 매일 정해진 시간에 안정적으로 실행된다.

1) Zuul Scheduler가 주기적으로 Job 트리거 (매일 02:00 UTC)

번역 업데이트 파이프라인의 시작점은 Zuul Scheduler의 주기적 작업 스케줄링이다. 이 시스템은 project-config/zuul.d/pipelines.yaml 파일에 정의된 periodic pipeline 설정을 기반으로 작동한다.

- pipeline:
    name: periodic
    post-review: true
    description: Jobs in this queue are triggered on a daily timer.
    manager: independent
    precedence: low
    trigger:
      timer:
        - time: '0 2 * * *'

Pipeline의 timer 설정은 cron 표현식과 유사한 형태로 정의되어 있는데, Zuul Scheduler는 이 설정을 지속적으로 모니터링하다가 해당 시간이 되면 periodic pipeline을 활성화시킨다. 이때 시스템은 단순히 하나의 작업을 실행하는 것이 아니라, 등록된 모든 프로젝트에 대해 번역 업데이트가 필요한지 확인하는 전체적인 스캔 과정을 시작하게 된다.

2) 프로젝트 스캔 및 대상 선별

Zuul Scheduler가 활성화되면 project-config/zuul.d/projects.yaml 파일에 정의되어 있는 모든 OpenStack 프로젝트들을 스캔한다. 이 파일에는 OpenStack 생태계에 속한 수백 개의 프로젝트들이 등록되어 있는데, 각 프로젝트마다 어떤 CI 템플릿을 사용할지 명시되어 있다.

- project:
    name: openstack/horizon
    queue: horizon
    templates:
      - official-openstack-repo-jobs
      - translation-jobs-master-stable
      - publish-to-pypi

우리가 마이그레이션할 주요 프로젝트 (horizon, magnum-ui, manila-ui, networking-bgpvpn, neutron-fwaas-dashboard, neutron-vpnaas-dashboard, octavia-dashboard 등) 의 공통점은 모두 ‘translation-jobs-master-stable’ 템플릿을 사용한다는 것이다. 이 템플릿은 Master 브랜치뿐만 아니라 여러 stable 브랜치들에 대해서도 번역 업데이트를 수행하도록 설계되어 있어서 OpenStack의 다양한 릴리스 버전들이 모두 최신 번역을 반영할 수 있도록 보장한다.

3) Job 생성

프로젝트 스캔이 완료되면 CI 시스템은 openstack-zuul-jobs/src/branch/master/zuul.d/project-templates.yaml 파일에 정의된 translation-jobs-master-stable 템플릿을 참조하여 실제 실행할 Job들을 동적으로 생성한다.

- project-template:
    name: translation-jobs-master-stable
    description: |
      Sync translations to translation server and back again for
      **master** and translated stable branches.

      This is intended for official OpenStack projects that have
      translations set up.
    post:
      jobs:
        - upstream-translation-update:
            branches:
              - master
              - stable/2025.1
              - stable/2024.2
              - stable/2024.1
    periodic:
      jobs:
        - propose-translation-update:
            branches:
              - master
              - stable/2025.1
              - stable/2024.2
              - stable/2024.1

주목할 점은 각 프로젝트마다 여러 개의 독립적인 Job이 생성된다는 것이다. 예를 들어 horizon 프로젝트의 경우 다음과 같은 형태로 여러 개의 스크립트 실행 명령이 생성된다.

  • master 브랜치용: ./propose_translation_update.sh horizon master

  • stable/2025.1 브랜치용: ./propose_translation_update.sh horizon stable/2025.1

  • stable/2024.2 브랜치용: ./propose_translation_update.sh horizon stable/2024.2

  • stable/2024.1 브랜치용: ./propose_translation_update.sh horizon stable/2024.1

이렇게 생성된 4개의 독립적인 Job들은 서로 다른 Zuul Executor에서 병렬로 실행된다. 이렇게 하면 하나의 브랜치에서 문제가 발생하더라도 다른 브랜치의 번역 업데이트에는 영향을 주지 않도록 격리된 환경을 제공할 수 있다.

4) Playbook 로딩 및 실행 환경 준비

각각의 Job이 생성되면 Zuul은 openstack-zuul-jobs/src/branch/master/zuul.d/project-templates.yaml에서 정의된 propose-translation-update Job의 구체적인 실행 방법을 확인한다. 이 정보에 따라 playbooks/translation/propose-translation-update.yaml 파일이 로드되고 실행된다.

Ansible Playbook 실행 구조

먼저 roles 섹션에 정의된 모든 역할들이 순차적으로 실행된 후, role이 완전히 완료되면 tasks 섹션이 실행된다.

- name: Propose Transaltion Updates
  hosts: all
  roles:
    - prepare-zanata-client
    - legacy-zuul-git-prep-upper-constraints

  tasks:
    - name: Run propose_translation_update.sh script
      command: "{{ ansible_user_dir }}/scripts/propose_translation_update.sh {{ zuul.project.short_name }} {{ zuul.branch }} {{ zuul.job }} {{ ansible_user_dir }}/{{ zuul.projects['opendev.org/openstack/horizon'].src_dir }}"
      args:
        chdir: "{{ zuul.project.src_dir }}"

Zanata 기반의 기존 설정에서는 두 개의 주요 role이 정의되어 있다.

  1. prepare-zanata-client : Zanata 클라이언트 도구 설치 및 설정

  2. legacy-zuul-git-prep-upper-constraints : Git 환경 및 종속성 제약 조건 준비

각 role은 완전히 독립적으로 실행되며, 이전 role이 완전히 완료되어야만 다음 role이 진행된다. 이렇게 순차적으로 실행하는 방식은 각 단계에서 발생할 수 있는 의존성 문제를 방지하고 실행 과정에서 발생하는 오류를 추적할 수 있게 해준다.

Role 실행 과정 분석

prepare-zanata-client role이 실행되면, openstack-zuul-jobs/src/branch/master/roles/prepare-zanata-client/tasks/main.yaml 파일에 정의된 작업들이 순서대로 수행된다.

이 과정에서 수행되는 주요 작업들을 살펴보면

  • Find java package name: 실행 환경의 운영체제(Ubuntu, CentOS, RHEL 등)를 식별하고 해당 OS에서 사용되는 Java 패키지의 정확한 이름을 확인(서로 다른 Linux 배포판들이 동일한 소프트웨어를 다른 패키지 이름으로 제공하는 경우가 많기 때문에 필요한 과정)
  • Install necessary packages: JRE, gettext, curl, wget 등 번역 업데이트 과정에서 필요한 모든 시스템 패키지들을 설치 (이때 패키지 관리자는 OS에 따라 자동으로 선택)
  • Ensure zanata install dir: Zanata 클라이언트가 설치될 디렉토리(/opt/zanata)를 생성하고 적절한 권한을 설정
  • Download Zanata client archive: Zanata 프로젝트의 공식 릴리스 페이지에서 최신 버전의 Zanata CLI 도구를 다운로드
  • Write out zanata config: Zanata 서버와의 연결에 필요한 설정 정보(.ini 형식)를 생성 (서버 URL, 인증 정보, 프로젝트별 설정 등을 포함)
  • Copy translation scripts: 실제 번역 업데이트를 수행하는 스크립트 파일들을 실행 권한과 함께 적절한 위치로 복사

5) Tasks 실행 및 동적 변수 치환

모든 role의 실행이 완료되면 playbook의 tasks 섹션이 실행된다. 이 단계에서는 Zuul이 제공하는 런타임 변수들을 실제 값으로 치환한다.

  tasks:
    - name: Run propose_translation_update.sh script
      command: "{{ ansible_user_dir }}/scripts/propose_translation_update.sh {{ zuul.project.short_name }} {{ zuul.branch }} {{ zuul.job }} {{ ansible_user_dir }}/{{ zuul.projects['opendev.org/openstack/horizon'].src_dir }}"
      args:
        chdir: "{{ zuul.project.src_dir }}"

Zuul 시스템은 각 Job 실행 시점에 여러 컨텍스트 정보를 Ansible 변수 형태로 제공한다.

주요 변수들과 치환 과정을 살펴보면

  • {{ ansible_user_dir }}: 실행 환경의 사용자 홈 디렉토리로 치환( /home/zuul)

  • {{ zuul.project.short_name }}: 현재 처리 중인 프로젝트의 이름으로 치환(ex. horizon)

  • {{ zuul.branch }}: 현재 작업 대상인 Git 브랜치명으로 치환 (ex. master 또는 stable/2025.1 등)

  • {{ zuul.job }}: 현재 실행 중인 Job의 이름으로 치환 (ex. propose-translation-update)

  • {{ zuul.project.src_dir }}: 프로젝트 소스코드를 체크아웃된 전체 경로로 치환 (ex. /home/zuul/src/opendev.org/openstack/horizon)

변수 치환을 통해 최종적으로 실행되는 명령어는 다음과 같은 형태가 된다.

command: "/home/zuul/scripts/propose_translation_update.sh horizon master propose-translation-update /home/zuul/src/opendev.org/openstack/horizon"

+ 실행 시의 작업 디렉토리도 지정된다.

args:
  chdir: "/home/zuul/src/opendev.org/openstack/horizon"

6) propose-translation-update.sh 실행

마지막 단계에서는 /home/zuul/scripts/propose_translation_update.sh 스크립트가 해당 프로젝트의 Git 디렉토리에서 실행된다.

스크립트 원본 파일: openstack-zuul-jobs/roles/prepare-zanata-client/files/propose_translation_update.sh at master - openstack-zuul-jobs - OpenDev: Free Software Needs Free Tools

>> role 실행 과정에서 적절한 위치로 복사된다.

스크립트가 실행되면서 수행하는 주요 작업들

  • 실행 환경을 확인하고 Git 사용자 정보를 설정하며, 작업에 필요한 환경 변수들을 초기화
  • 프로젝트 유형을 자동으로 감지하고 적절한 처리 로직을 적용 - 문서/ReactJS/web UI/Python/Django 대시보드 등 다양한 유형
  • 설정된 Zanata 서버로부터 해당 프로젝트의 모든 언어별 번역 파일을 일괄적으로 다운로드 (재시도 로직 포함)
  • 특수한 처리 과정 포함
    • 중국어 로케일의 경우 zh_CNzh_Hans로, zh_TWzh_Hant로 변환 (간체/번체 표기법 관련 국제 표준 준수)
    • 릴리스노트의 경우에는 master 브랜치에서만 처리되도록 제한되어 있음
    • 문서 번역은 DOC_TARGET 환경 변수에 등록된 특정 프로젝트들에서만 활성화
  • 다운로드된 번역 파일들을 프로젝트의 디렉토리 구조에 맞게 재배치
    • 불필요한 임시 파일 정리
    • 번역 파일 형식 검증
    • 필요 시 압축 수행
  • 모든 번역 파일 처리가 완료되면 변경사항을 Git 커밋으로 생성하고 Gerrit 코드 리뷰 시스템에 자동으로 제출
2개의 좋아요