Kubernetes: Kubernetes 작업 및 Helm hook을 사용하여 SQL 마이그레이션 실행


Kubernetes에서 실행되는 프로젝트가 있습니다. 배포 중에 SQL 마이그레이션을 실행해야 합니다.
마이그레이션을 실행하려면 Github 저장소를 복제하고 실제 마이그레이션을 실행해야 합니다.
현재 이것은 Kubernetes initContainers로 완성된 것으로 그 중 두 개가 있다 — 첫 번째 저장소 git 는 마이그레이션 파일이 있는 저장소를 복제하여 Kubernetes Volume 로 옮긴 다음 다른 저장소 sql-migrate 는 공유 볼륨에서 마이그레이션을 실행합니다.
그럼에도 불구하고 이러한 방법에는 몇 가지 문제가 존재한다.
  • 우리가 새로운pod를 실행할 때마다 initContainers와 이전
  • 을 실행합니다
  • 몇 개의 기중기를 배치하면 — 마이그레이션이 실행될 때마다
  • 마이그레이션이 실행되고 Kubernetes에 응답하지 않을 경우 — 그것은 이전이 완료되지 않은 상태에서 살해될 수 있다
  • 상기 모든 상황을 피하기 위해, 우리는 Kubernetes Job 프로세스를 다시 설정하고, 하나의pod만 실행하고, Helm Hooks 를 추가하여 배치 기간의 이동을 촉발합니다.
    주의: 여기 kk는kubectl의 별명입니다.

    준비


    Docker 이미지


    우선, 21시와git, 그리고 https://github.com/rubenv/sql-migrate로 우리만의 Docker 이미지를 만듭니다.
    Dockerfile 만들기:
    FROM golang:alpine AS builder
    RUN apk add --no-cache git gcc g++
    RUN go get -v github.com/rubenv/sql-migrate/sql-migrate
    RUN mv /go/bin/sql-migrate /bin/sql-migrate
    
    구축 및 추진:
    $ docker build -t projectname/sql-migrate-git .
    $ docker push projectname/sql-migrate-git
    

    Git 인증


    두 번째는 Github 인증입니다.
    이 때, 우리의git 용기는 Kubernetes Secrets에 저장된 RSA 키를 통해 인증을 한 다음, 환경 변수를 통해pod로 전달하고, bash 스크립트/opt/git/git.sh에서 이 키를 가져옵니다. 이 스크립트는 용기 안에 키 파일/root/.ssh/id_rsa을 만드는 데 사용되며, 이 키는 인증에 사용됩니다.
    현재 배포 중인 initContainers 은 다음과 같습니다.
    ...
          initContainers:
          - name: git-clone
            image: projectname/git-cloner
            env:
            - name: SSH_PRIVATE_KEY
              valueFrom:
                secretKeyRef:
                  name: git-ssh-key
                  key: id_rsa
            - name: REPOSITORY_URL
              value: {{ .Values.git.repo }}
            - name: GIT_BRANCH
              value: {{ .Values.git.branch }}
            command: ['sh', '-c', '/opt/git/git.sh']
            volumeMounts:
              - name: git-volume
                mountPath: "/git"
                readOnly: false
          - name: init-migration
            image: fufuhu/sql-migrate:latest
            command: ['sh', '-c', 'while [! -d /git/db/migrations]; do sleep 2; done && sleep 2; /bin/sql-migrate up -config=/config/config.yaml -env=main']
            volumeMounts:
              - name: migration-config
                mountPath: "/config/"
                readOnly: true
              - name: git-volume
                mountPath: "/git"
                readOnly: false
    ...
    
    많은 절차, 많은 대상, 복잡한 과정.
    대신 환경 변수를 컨테이너에 전달하고 HTTPS를 통해 저장소를 복제할 수 있는 로그인 이름과 Github token을 사용합니다.
    테스트를 해보겠습니다.
    ~ # export GIT_AUTHUSER=backend-user
    ~ # export GIT_AUTHKEY=cdc***0fe
    ~ # git clone
    [https://$GIT_AUTHUSER:[email protected]/projectname-dev/backend-services.git](https://%24GIT_AUTHUSER:%[email protected]/projectname-dev/backend-services.git)
    Cloning into ‘backend-services’…
    …
    Receiving objects: 100% (5115/5115), 846.55 KiB | 1.30 MiB/s, done.
    Resolving deltas: 100% (2826/2826), done.
    
    좋아, "효과가 있어")

    Kubernetes의 SQL 마이그레이션


    이제 Kubernetes 작업을 설명하기 위해 목록 파일 templates/appname-api-migrations.yaml 을 작성할 수 있습니다. 이 작업은 나중에 Helm 연결로 시작됩니다.

    Kubernetes 작업


    git 클론


    우선, 업무가 정상적으로 진행되도록 확보하다 — Helm 변수와 값이 없는 상태에서 작성합니다. 모든pod의 환경 변수는 순수한 텍스트 값으로 설정됩니다.
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: "migration-job"
      labels:
      annotations:
    spec: 
      backoffLimit: 0
      template:
        metadata:
          name: "migration-job-pod"
        spec:
          restartPolicy: Never
          containers:
          - name: db-migrations
            image: projectname/sql-migrate-git:latest
            command: ["/bin/sh", "-c"]
            args:
              - git clone --single-branch --branch develop [https://backend-user:cdc***[email protected]/projectname/backend-services.git](https://backend-user:cdc***[email protected]/projectname/backend-services.git) &&
                ls -l backend-services/db/migrations
    
    git clone 에서, 우리는 용기가 실패할 때 용기를 다시 시작하지 않도록 설정했습니다. 왜냐하면 우리는 이전 실패를 보고 싶기 때문입니다. restartPolicy에서도 마찬가지입니다. 실패하면pod를 다시 만들지 않고 실패 상태만 유지하는 작업입니다.backoffLimit=0 에서 Jenkins 작업 설정 분기, git clone 에서 사용자 및 URL을 설정하고 인증 토큰을 Helm secrets 에 보관한 후 환경 변수로 이동합니다.
    작업을 만들려면 다음과 같이 하십시오.
    $ kk -n eks-dev-1-appname-api-ns apply -f appname-api-jobs.yaml
    job.batch/migration-job created
    
    로그 확인:
    $ kk -n eks-dev-1-appname-api-ns logs job/migration-job
    Cloning into ‘backend-services’…
    total 20
    -rw-r — r — 1 root root 538 Oct 24 12:20 BS_1_init_schema.up.sql
    -rw-r — r — 1 root root 180 Oct 24 12:20 BS_2_add_brand_field.up.sql
    -rw-r — r — 1 root root 225 Oct 24 12:20 BS_3_alter_table.up.sql
    -rw-r — r — 1 root root 194 Oct 24 12:20 BS_4_add_created_at_field.sql
    -rw-r — r — 1 root root 272 Oct 24 12:20 BS_5_alter_table_nourishment_diet.up.sql
    
    저장소가 복제되어 마이그레이션 파일에 액세스할 수 있습니다.
    크레인 상태 검사:
    $ kk -n eks-dev-1-appname-api-ns get pod
    NAME READY STATUS RESTARTS AGE
    migration-job-f72vs 0/1 Completed 0 9s
    
    작업 상태:
    $ kk -n eks-dev-1-appname-api-ns get job
    NAME COMPLETIONS DURATION AGE
    migration-job 1/1 2s 5s
    
    이제 정확한 마이그레이션 프로세스를 계속 수행할 수 있습니다.

    비밀.


    마이그레이션을 실행하려면 Kubernetes ConfigMap 에 저장되는 구성 파일을 만들어야 하지만 이 파일에는 데이터베이스 암호를 설정해야 합니다.
    ConfigMap 및 파일에 명문으로 저장하는 것은 좋지 않지만 values.yaml 파일에서 환경 변수를 사용할 수 있도록 하려면 문서를 확인하십시오 - https://github.com/rubenv/sql-migrate#as-a-standalone-tool
    따라서pod에 sql-migrate라는 변수를 만들고 실제 암호를 Kubernetes 기밀에 보관합니다. 나중에 Helm에서 Helm secrets 암호화를 사용하여 도표의 값에 저장합니다.
    마찬가지로 본 기밀에서는 $DB_PASSWORD 명령에 사용할 환경 변수의 값을 저장합니다.
    여전히 $GIT_TOKEN 에 비밀을 추가합니다.
    ...
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: backend-db-password
    type: Opaque
    stringData:
      db_password: password
      git_token: cdc***0fe
    
    작업의 git clone 에 변수를 추가하고 templates/appname-api-migrations.yaml 를 업데이트하여 spec.containers.env 변수를 사용합니다.
    ...
          containers:
          - name: db-migrations
            image: projectname/sql-migrate-git:latest
            command: ["/bin/sh", "-c"]
            args:
              - git clone --single-branch --branch develop [https://backend-user:[email protected]/projectnamev/backend-services.git](https://backend-user:%[email protected]/projectnamev/backend-services.git) &&
                ls -l backend-services/db/migrations;
                cat /config/config.yaml
            env:
            - name: GIT_TOKEN
              valueFrom:
                secretKeyRef:
                  name: backend-db-password
                  key: git_token
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: backend-db-password
                  key: db_password
    ...
    

    매핑 구성


    다음으로 ConfigMap을 작성하여 git clone$GIT_TOKEN 컨텐트를 보존하고 /config/config.yaml 변수를 사용합니다.
    ...
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: migration-config
    data:
      config.yaml: |
        main:
          dialect: mysql
          datasource: backend-user:${DB_PASSWORD}@tcp(stage.backend-db3-master.example.com:3306)/dbname?parseTime=true
          dir: backend-services/db/migrations
          table: backend_services_migrations
    
    작업의 pod 템플릿에 볼륨을 추가하고 sql-migrate 에서 볼륨을 로드하여 ConfigMap을 $DB_PASSWORD 파일로 만듭니다.
    작업의 전체 목록은 다음 목록입니다.
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: "migration-job"
      labels:
      annotations:
    spec: 
      backoffLimit: 0
      template: 
        metadata:
          name: "migration-job-pod"
        spec:
          restartPolicy: Never
          containers:
          - name: db-migrations
            image: projectname/sql-migrate-git:latest
            command: ["/bin/sh", "-c"]
            args:
              - git clone --single-branch --branch develop [https://backend-user:[email protected]/projectname/backend-services.git](https://backend-user:%[email protected]/projectname/backend-services.git) &&
                ls -l backend-services/db/migrations;
                cat /config/config.yaml
            env:
            - name: GIT_TOKEN
              valueFrom:
                secretKeyRef:
                  name: backend-db-password
                  key: git_token
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: backend-db-password
                  key: db_password
            volumeMounts:
            - name: migration-config
              mountPath: "/config/config.yaml"
              subPath: "config.yaml"
              readOnly: true
          volumes:
            - name: migration-config
              configMap: 
                name: migration-config
                items:
                - key: "config.yaml"
                  path: "config.yaml"
    ...
    
    실행:
    $ kk -n eks-dev-1-appname-api-ns apply -f appname-api-jobs.yaml
    job.batch/migration-job created
    secret/backend-db-password created
    configmap/migration-config created
    
    검사:
    $ kk -n eks-dev-1-appname-api-ns logs job/migration-job
    Cloning into ‘backend-services’…
    total 20
    -rw-r — r — 1 root root 538 Oct 24 13:41 BS_1_init_schema.up.sql
    -rw-r — r — 1 root root 180 Oct 24 13:41 BS_2_add_brand_field.up.sql
    -rw-r — r — 1 root root 225 Oct 24 13:41 BS_3_alter_table.up.sql
    -rw-r — r — 1 root root 194 Oct 24 13:41 BS_4_add_created_at_field.sql
    -rw-r — r — 1 root root 272 Oct 24 13:41 BS_5_alter_table_nourishment_diet.up.sql
    main:
    dialect: mysql
    datasource: backend-user:${DB_PASSWORD}@tcp(stage.backend-db3-master.example.com:3306)/dbname?parseTime=true
    dir: backend-services/db/migrations
    table: backend_services_migrations
    
    좋다 — 저장소가 복제되고 구성 파일이 생성되었습니다.

    마이그레이션 실행


    현재 마이그레이션 프로세스는 spec.containers 옵션과 두 번째 명령으로 설명할 수 있습니다.
    ...
            args:
              - git clone --single-branch --branch develop [https://backend-user:[email protected]/projectname/backend-services.git](https://backend-user:%[email protected]/projectname/backend-services.git) &&
                ls -l backend-services/db/migrations &&
                cat /config/config.yaml &&
                /bin/sql-migrate up -config=/config/config.yaml -env=main -dryrun &&
                /bin/sql-migrate status -config=/config/config.yaml -env=main
    ...
    
    로그를 실행하고 확인합니다.
    $ kk -n eks-dev-1-appname-test-migrations-ns logs job/migration-job
    Cloning into ‘backend-services’…
    total 20
    -rw-r — r — 1 root root 538 Oct 24 14:02 BS_1_init_schema.up.sql
    -rw-r — r — 1 root root 180 Oct 24 14:02 BS_2_add_brand_field.up.sql
    -rw-r — r — 1 root root 225 Oct 24 14:02 BS_3_alter_table.up.sql
    -rw-r — r — 1 root root 194 Oct 24 14:02 BS_4_add_created_at_field.sql
    -rw-r — r — 1 root root 272 Oct 24 14:02 BS_5_alter_table_nourishment_diet.up.sql
    main:
    dialect: mysql
    datasource: backnd-user:${DB_PASSWORD}@tcp(stage.backend-db3-master.example.com:3306)/dbname?parseTime=true
    dir: backend-services/db/migrations
    table: backend_services_migrations
    + — — — — — — — — — — — — — — — — — — — — — + — — — — — — — — — — — — — — — -+
    | MIGRATION | APPLIED |
    + — — — — — — — — — — — — — — — — — — — — — + — — — — — — — — — — — — — — — -+
    | BS_1_init_schema.up.sql | 2020–05–07 12:21:25 +0000 UTC |
    | BS_2_add_brand_field.up.sql | 2020–05–12 14:31:17 +0000 UTC |
    | BS_3_alter_table.up.sql | 2020–05–13 06:17:25 +0000 UTC |
    | BS_4_add_created_at_field.sql | 2020–07–21 09:55:49 +0000 UTC |
    | BS_5_alter_table_nourishment_diet.up.sql | 2020–07–21 09:55:49 +0000 UTC |
    + — — — — — — — — — — — — — — — — — — — — — + — — — — — — — — — — — — — — — -+
    
    이제 키잡이에 들어갈 수 있습니다.

    투구 틀


    낡은 도표에서 우리는 무엇을 해야 합니까?
  • 뜯어내기volumeMounts
  • 이전 기밀 삭제
  • /config/config.yaml 에서 새 변수 값 이동
  • 영패와 데이터베이스 비밀번호를 -dryrun로 이동
  • 여기에는 Helm 배포 중에 마이그레이션 프로세스를 트리거하기 위해 주석을 추가합니다.initContainers 작업에 추가:
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: {{ .Chart.Name }}-migration-job
      labels:
      annotations:
        "helm.sh/hook": pre-install,pre-upgrade
        "helm.sh/hook-weight": "-1"
        "helm.sh/hook-delete-policy": before-hook-creation
    spec: 
      backoffLimit: 0
    ...
    
    여기:
  • values.yaml: secrets.yaml 또는 annotations 이전에 작업을 실행합니다(우리의 Jenkins 파이프에서 "helm.sh/hook": pre-install,pre-upgrade부터)
  • helm install: 우선 작업에 사용할 ConfigMap과 Secret을 만들어야 하기 때문에 리소스 생성 우선 순위를 작업보다 작게 설정합니다
  • .
  • upgrade: 기본값은 갈고리를 만들기 전 (검사 documentation, 테스트 목적에 if를 설정한 다음 갈고리로 변경할 수 있습니다 (단, 이 경우 마이그레이션에 실패하면 로그를 검사할 수 없습니다)
  • ConfigMap에 helm secrets upgrade --install 블록을 추가하고 작업에서 보다 작은 "helm.sh/hook-weight": "-1" 블록을 사용하여 비밀을 유지합니다.
    ConfigMap 목록에는 현재 전체 내용이 포함되어 있습니다.
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: migration-config
      annotations:
        "helm.sh/hook": pre-install,pre-upgrade
        "helm.sh/hook-weight": "-5"
        "helm.sh/hook-delete-policy": before-hook-creation
    data:         
      config.yaml: |
        main:
          dialect: {{ .Values.backendConfig.db.driver }}
          datasource: {{ .Values.backendConfig.db.user }}:${DB_PASSWORD}@tcp({{ .Values.backendConfig.db.host }}:{{ .Values.backendConfig.db.port }})/{{ .Values.backendConfig.db.database }}?parseTime=true
          dir: backend-services/db/migrations
          table: {{ .Values.backendConfig.db.migrationsTable }}
    
    비밀 중 하나:
    ---     
    apiVersion: v1
    kind: Secret
    metadata:
      name: {{ .Chart.Name }}-migration-secrets
      annotations: 
        "helm.sh/hook": pre-install,pre-upgrade
        "helm.sh/hook-weight": "-10"
        "helm.sh/hook-delete-policy": before-hook-creation
    type: Opaque
    stringData:
      backend-db-password: {{ .Values.backendConfig.db.password }}
      git_token: {{ .Values.git.token }}
    
    그리고 작업:
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: {{ .Chart.Name }}-migration-job
      labels:
      annotations:
        "helm.sh/hook": pre-install,pre-upgrade
        "helm.sh/hook-weight": "-1"
        "helm.sh/hook-delete-policy": before-hook-creation
    spec:
      backoffLimit: 0
      template:
        metadata:
          name: {{ .Chart.Name }}-migration-job-pod
        spec:
          restartPolicy: Never
          containers:
          - name: {{ .Chart.Name }}-db-migrations
            image: projectname/sql-migrate-git:latest
            command: ["/bin/sh", "-c"]
            args: 
              - git clone --single-branch --branch {{ .Values.git.branch }} [https://{{](https://%7B%7B) .Values.git.user }}:$GIT_TOKEN@{{ .Values.git.repo }} &&
                ls -l backend-services/db/migrations &&
                cat /config/config.yaml &&
                /bin/sql-migrate up -config=/config/config.yaml -env=main || exit 1;
                /bin/sql-migrate status -config=/config/config.yaml -env=main
            env:
            - name: GIT_TOKEN
              valueFrom:
                secretKeyRef:
                  name: {{ .Chart.Name }}-migration-secrets
                  key: git_token
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: {{ .Chart.Name }}-migration-secrets
                  key: backend-db-password
            volumeMounts:
            - name: migration-config
              mountPath: "/config/config.yaml"
              subPath: "config.yaml"
              readOnly: true
          volumes:
            - name: migration-config
              configMap: 
                name: migration-config
                items:
                - key: "config.yaml"
                  path: "config.yaml"
    
    여기에서 "helm.sh/hook-delete-policy"을(를) annotations에 추가하여 이전 과정에서 오류가 발생하면 작업이 실패하기 때문에 배치 과정을 시작하지 않습니다.
    Jenkins에서 실행:


    이 갈고리들 중에서 첫 번째 비밀은 원형대로 만들어진 것hook-weight, 그 다음에 맵을 설정하고 마지막에 작업하는 것을 볼 수 있다.
    이제 배포 프로세스는 다음과 같이 보입니다.

    먼저 기밀을 삭제하고 맵과 작업 자원을 설정합니다(근거exit 1.
    작업 상태 확인:
    $ kk -n eks-stage-1-appname-api-ns get job
    NAME COMPLETIONS DURATION AGE
    appname-api-migration-job 1/1 3s 6m21s
    
    그 로그:
    $ kk -n eks-stage-1-appname-api-ns logs job/appname-api-migration-job
    Cloning into ‘backend-services’…
    total 20
    -rw-r — r — 1 root root 538 Oct 26 11:32 BS_1_init_schema.up.sql
    -rw-r — r — 1 root root 180 Oct 26 11:32 BS_2_add_brand_field.up.sql
    -rw-r — r — 1 root root 225 Oct 26 11:32 BS_3_alter_table.up.sql
    -rw-r — r — 1 root root 194 Oct 26 11:32 BS_4_add_created_at_field.sql
    -rw-r — r — 1 root root 272 Oct 26 11:32 BS_5_alter_table_nourishment_diet.up.sql
    main:
    dialect: mysql
    datasource: backend-user:${DB_PASSWORD}@tcp(stage.backend-db3-master.example.com:3306)/dbname?parseTime=true
    dir: backend-services/db/migrations
    table: backend_services_migrations
    Applied 0 migrations
    + — — — — — — — — — — — — — — — — — — — — — + — — — — — — — — — — — — — — — -+
    | MIGRATION | APPLIED |
    + — — — — — — — — — — — — — — — — — — — — — + — — — — — — — — — — — — — — — -+
    | BS_1_init_schema.up.sql | 2020–05–07 12:21:25 +0000 UTC |
    | BS_2_add_brand_field.up.sql | 2020–05–12 14:31:17 +0000 UTC |
    | BS_3_alter_table.up.sql | 2020–05–13 06:17:25 +0000 UTC |
    | BS_4_add_created_at_field.sql | 2020–07–21 09:55:49 +0000 UTC |
    | BS_5_alter_table_nourishment_diet.up.sql | 2020–07–21 09:55:49 +0000 UTC |
    + — — — — — — — — — — — — — — — — — — — — — + — — — — — — — — — — — — — — — -+
    
    마지막 /bin/sql-migrate u 이후 마이그레이션 파일에 변경 사항이 없기 때문에 0 번의 마이그레이션을 적용했습니다.
    모두 완성하다.
    최초 발표RTFM: Linux, DevOps, and system administration.

    좋은 웹페이지 즐겨찾기