스타일 가이드

이 문서는 GitLab Helm 차트 개발에 대한 가이드라인과 모범 사례를 설명합니다.

네이밍 규칙

우리는 함수 이름과 values.yaml에서 사용되는 속성에 대해 camelCase를 사용합니다.

예: gitlab.assembleHost

템플릿 함수는 해당하는 대상 파일의 채워진 값과 일치하도록 차트에 따라 네임스페이스에 배치됩니다. 차트 global 함수는 일반적으로 gitlab.* 네임스페이스에 속합니다.

예시:

  • gitlab.redis.host: gitlab 차트의 일부로 Redis 서버의 호스트 이름을 제공합니다.
  • registry.minio.url: registry 차트의 일부로 MinIO 호스트의 URL을 제공합니다.

values.yaml에 대한 일반 구조

많은 차트는 동일한 정보를 제공해야 합니다. 예를 들어, 여러 차트에 Redis 및 PostgreSQL 연결 설정을 제공해야 하는 경우입니다. 여기서 해당 설정의 표준 이름과 구조를 설명합니다.

다른 서비스에 연결

redis:
  host: redis.example.com
  serviceName: redis
  port: 8080
    sentinels:
    - host: sentinel1.example.com
      port: 26379
  password:
    secret: gitlab-redis
    key: redis-password
  • redis - 현재 차트에서 연결해야 하는 항목의 이름
  • host - 기본적으로 serviceName 사용을 재정의하며, 주석 처리하고 예제로 0.0.0.0을 사용합니다. Redis Sentinels를 사용하는 경우 host 속성을 sentinel.conf에서 지정된 클러스터 이름으로 설정해야 합니다.
  • serviceName - 기본적으로 호스트 대신에 사용할 수 있도록 구성되며, Kubernetes 서비스 이름을 사용하여 연결합니다.
  • port - 연결할 포트입니다. 기본 설정으로 주석 처리하고 기본 포트를 사용합니다.
  • password - 비밀번호를 포함하는 Kubernetes Secret의 설정을 정의합니다.
  • sentinels.[].host - Redis HA 설정을 위한 Redis Sentinel 서버의 호스트 이름을 정의합니다.
  • sentinels.[].port - Redis Sentinel 서버에 연결할 포트를 정의합니다. 기본값은 26379입니다.

참고: 현재 Redis Sentinel 지원은 GitLab 차트에서 별도로 배포된 Sentinels만 지원합니다. 결과적으로, GitLab 차트를 통한 Redis 배포는 redis.install=false로 비활성화되어야 합니다. Redis 비밀번호를 포함하는 Secret은 GitLab 차트를 배포하기 전에 수동으로 생성되어야 합니다.

보안 정보 공유

우리는 비밀번호와 같은 민감한 정보를 저장하고 다른 차트/팟 간에 공유하기 위해 비밀 정보(Secrets)를 사용합니다.

우리가 그것을 사용하는 일반적인 분야는 다음과 같습니다:

  • TLS/SSL 인증서 - TLS/SSL 인증서 공유
  • 비밀번호 - Redis 비밀번호 공유
  • 인증 토큰 - 서비스 간 인증 토큰 공유
  • 기타 비밀 정보 - JWT 인증서 및 서명 키와 같은 다른 비밀 정보 공유

TLS/SSL 인증서

TLS/SSL 인증서는 유효한 Kubernetes TLS Secret로 예상됩니다.

예를 들어, 레지스트리를 설정하는 경우:

registry:
  tls:
    secretName: <TLS 비밀 이름>

차트 간에 TLS 인증서를 공유하는 경우 전역 값으로 정의해야 합니다.

global:
  ingress:
    tls:
      secretName: <TLS 비밀 이름>

비밀번호

예를 들어, redis가 소유한 차트이며, 다른 차트가 redis 비밀번호를 참조해야 하는 경우입니다.

소유 차트는 다음과 같이 비밀번호 Secret을 정의해야 합니다:

password:
  secret: <비밀 이름>
  key: <검색할 Secret 안의 키 이름>

다른 차트는 다음과 같이 동일한 비밀번호 Secret을 공유해야 합니다:

redis:
  password:
    secret: <비밀 이름>
    key: <Secret 내에서 검색할 키 이름>

인증 토큰

소유 차트는 다음과 같이 authToken Secret을 정의해야 합니다:

authToken:
  secret: <비밀 이름>
  key: <Secret 내에서 검색할 키 이름>

다른 차트는 다음과 같이 동일한 비밀번호 Secret을 공유해야 합니다:

gitaly:
  authToken:
    secret: <비밀 이름>
    key: <Secret 내에서 검색할 키 이름>

예를 들어, gitaly가 소유한 차트이며, 다른 차트가 gitaly authToken을 참조해야 하는 경우입니다.

기타 Secrets

registry 또는 gitaly GPG 서명 키와 같은 다른 Secrets의 경우, authTokenpassword Secrets와 동일한 형식을 사용합니다.

한 차트에서 다른 차트로 이러한 Secrets를 공유하려면, 다음과 유사한 구성을 제공하십시오. registry의 JWT 서명 인증서가 다른 차트와 공유되는 예제입니다.

소유 차트는 다음과 같이 Secret을 정의해야 합니다:

certificate:
  secret: <비밀 이름>
  key: <Secret 내에서 검색할 키 이름>

다른 차트는 다음과 같이 동일한 Secret을 공유해야 합니다:

registry:
  certificate:
    secret: <비밀 이름>
    key: <Secret 내에서 검색할 키 이름>

함수 사용 선호도

우리는 gotmpl, Sprig 및 Helm에서 사용 가능한 여러 함수에 대해 차트 개발에 대한 선호도 집합을 개발했습니다. 다음 섹션에서 이러한 일부에 대해 설명하고 배경을 설명합니다.

indent 대신 nindent 사용

가능한 경우, indent 함수 대신 nindent 함수를 사용합니다. 이 선호도는 가독성에 기반하며, 특히 우리 차트의 경우에는 복잡할 수 있습니다. nindent의 선호 사용은 커뮤니티 전반에 퍼지며, 이제 helm create 명령으로 생성된 템플릿에서도 기본값으로 사용됩니다.

이유를 명확히 보여주는 두 가지 스니펫 예제를 살펴보겠습니다.

쉽게 읽을 수 있음

  gitlab.yml.erb: |
    production: &base
      gitlab:
        host: {{ template "gitlab.gitlab.hostname" . }}
        https: {{ hasPrefix "https://" (include "gitlab.gitlab.url" .) }}
        {{- with .Values.global.hosts.ssh }}
        ssh_host: {{ . | quote }}
        {{- end }}
        {{- with .Values.global.appConfig }}
        max_request_duration_seconds: {{ default (include "gitlab.appConfig.maxRequestDurationSeconds" $) .maxRequestDurationSeconds }}
        impersonation_enabled: {{ .enableImpersonation }}
        application_settings_cache_seconds: {{ .applicationSettingsCacheSeconds | int }}
        usage_ping_enabled: {{ eq .enableUsagePing true }}
        username_changing_enabled: {{ eq .usernameChangingEnabled true }}
        issue_closing_pattern: {{ .issueClosingPattern | quote }}
        default_theme: {{ .defaultTheme }}
        {{- include "gitlab.appConfig.defaultProjectsFeatures.configuration" $ | nindent 8 }}
        webhook_timeout: {{ .webhookTimeout }}
        {{- end }}
        trusted_proxies:
        {{- if .Values.trusted_proxies }}
          {{- toYaml .Values.trusted_proxies | nindent 10 }}
        {{- end }}
        time_zone: {{ .Values.global.time_zone | quote }}
        {{- include "gitlab.outgoing_email_settings" . | nindent 8 }}
      {{- with .Values.global.appConfig }}
      {{- if .incomingEmail.enabled }}
      {{- include "gitlab.appConfig.incoming_email" . | nindent 6 }}
      {{- end }}
      {{- include "gitlab.appConfig.cronJobs" . | nindent 6 }}
      gravatar:

읽기 어려움

  gitlab.yml.erb: |
    production: &base
      gitlab:
        host: {{ template "gitlab.gitlab.hostname" . }}
        https: {{ hasPrefix "https://" (include "gitlab.gitlab.url" .) }}
        {{- with .Values.global.hosts.ssh }}
        ssh_host: {{ . | quote }}
        {{- end }}
        {{- with .Values.global.appConfig }}
        max_request_duration_seconds: {{ default (include "gitlab.appConfig.maxRequestDurationSeconds" $) .maxRequestDurationSeconds }}
        impersonation_enabled: {{ .enableImpersonation }}
        usage_ping_enabled: {{ eq .enableUsagePing true }}
        username_changing_enabled: {{ eq .usernameChangingEnabled true }}
        issue_closing_pattern: {{ .issueClosingPattern | quote }}
        default_theme: {{ .defaultTheme }}
        {{- include "gitlab.appConfig.defaultProjectsFeatures.configuration" $ | indent 8 }}
        webhook_timeout: {{ .webhookTimeout }}
        {{- end }}
        trusted_proxies:
        {{- if .Values.trusted_proxies }}
        {{- toYaml .Values.trusted_proxies | indent 10 }}
        {{- end }}
        time_zone: {{ .Values.global.time_zone | quote }}
        {{- include "gitlab.outgoing_email_settings" . | indent 8 }}
        {{- with .Values.global.appConfig }}
        {{- if .incomingEmail.enabled }}
        {{- include "gitlab.appConfig.incoming_email" . | indent 6 }}
        {{- end }}
        {{- include "gitlab.appConfig.cronJobs" . | indent 6 }}
      gravatar:

관련 이슈: #729 Refactoring: Helm templates

템플릿에서 toYaml을 사용하는 시점

템플릿 파일에서 toYaml을 기본으로 사용하는 것은 권장되지 않습니다. 이는 Kubernetes 및 원하는 커뮤니티 구성의 모든 기능을 지원하는 데 부담을 주기 때문입니다. 우리는 주로 최소한의 구성을 사용하여 합리적인 기본값을 제공하는 데 중점을 두고 있습니다. 두 번째로는 Kubernetes의 보다 고급 사용자들을 위해 기본값을 재정의할 수 있는 능력을 제공하는 것입니다. 이는 특정 시나리오에 따라 각각의 경우에 수행되어야 하며, 양쪽 옵션 중에 어느 것이 너무 번거롭게 지원되거나 유지하기에 불필요하게 복잡한 템플릿을 제공할 수 있는지에 대한 명확한 시나리오가 있습니다.

합리적인 기본값과 재정의할 수 있는 능력이 있는 좋은 예는 레지스트리 서브차트의 수평 팟 오토스케일러 구성에서 찾을 수 있습니다. 우리는 지원하기 쉬운 최소한의 구성을 기본값으로 제공하여 복잡성을 피할 수 있도록 노출하면서, 커뮤니티에게 하나의 구성 옵션인 targetAverageUtilization만 노출합니다. HPA는 더 많은 유연성을 제공할 수 있기 때문에 고급 사용자는 다양한 메트릭을 대상으로 삼고 싶어할 수 있으며, 이는 사용자가 보다 복잡한 HPA 구성을 제공할 수 있도록 하는 if 문을 사용할 수 있는 완벽한 예입니다.

  metrics:
  {{- if not .Values.hpa.customMetrics }}
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          targetAverageUtilization: {{ .Values.hpa.cpu.targetAverageUtilization }}
  {{- else -}}
    {{- toYaml .Values.hpa.customMetrics | nindent 4 -}}
  {{- end -}}

위의 예에서 최소 구성은 values.yaml을 업데이트하여 targetAverageUtilization을 간단히 변경할 수 있습니다.

더 나은 메트릭을 식별한 고급 사용자들은 .customMetrics를 설정하여 이 지나치게 단순한 HPA 구성을 재정의할 수 있습니다. 이는 HPA 메트릭 배열에 대해 Kubernetes API와 호환되는 구성을 포함하는 배열로 설정됩니다.

고급 사용자들도 자신의 구성 파일을 번거롭지 않게 유지할 수 있도록 중요합니다.

템플릿 도우미 개발

차트 템플릿 도우미는 templates/_helpers.tpl에 있습니다. 이것들은 차트 내에서 사용되는 이름이 지정된 템플릿을 포함합니다.

이러한 템플릿을 사용할 때는 Go 템플릿 구문에 관련하여 몇 가지 항목을 주의해야 합니다.

동작으로부터 출력되지 않는 값을 포착하기

Go 템플릿 구문에서 모든 작업( {{ }}로 표시됨)은 문자열을 출력할 것으로 기대됩니다. 단, 제외됨 구조 조작 (define, if, with, range) 및 변수 할당을 포함하고 있습니다.

따라서 때로는 출력되면 안 되는 값들을 잡기 위해 변수 할당을 사용해야 할 수 있습니다.

예를 들어:

{{- $details := .Values.details -}}
{{- $_ := set $details "serviceName" "example" -}}
{{ template "serviceHost" $details }}

위의 예에서 우리는 출력을 의도하지 않은 데이터를 템플릿 함수에 전달하기 전에 Map에 추가 데이터를 추가하고자 합니다. 우리는 set 함수의 출력을 $_ 변수에 할당함으로써 출력을 포착했습니다. 이 할당 없이는 템플릿은 set 함수의 결과물(수정한 Map을 반환함)을 문자열로 출력하려 시도할 것입니다.

구조 조작 사이에서 변수 전달하기

Go 템플릿 구문은 변수의 초기화(:=)와 할당(=)을 명확하게 구분하고 있으며, 이는 범위에 영향을 받습니다.

그 결과 범위 내에서 선언된 변수는 범위 바깥에서 사용할 수 없지만, 범위 밖에서 존재하는 변수를 다시 초기화할 수 있습니다.

예를 들어:

{{- define "exampleTemplate" -}}
{{- $someVar := "default" -}}
{{- if true -}}
{{-   $someVar := "desired" -}}
{{- end -}}
{{- $someVar -}}
{{- end -}}

위의 예에서 exampleTemplate을 호출하면 항상 default를 반환하게 됩니다. 그 이유는 desired를 포함하고 있는 변수가 if 범위 내에서만 접근 가능하기 때문입니다.

이 문제를 해결하기 위해 우리는 여러 범위 내에서 변경할 값들을 보유하기 위해 사전(Dictionary)을 사용하거나 명시적으로 할당 연산자 (= 대신 :=)를 사용하여 문제를 피하려 시도합니다.

문제를 피하는 예:

{{- define "exampleTemplate" -}}
{{- if true -}}
{{-   "desired" -}}
{{- else -}}
{{-   "default" -}}
{{- end -}}

사전을 사용하는 예:

{{- define "exampleTemplate" -}}
{{- $result := dict "value" "default" -}}
{{- if true -}}
{{-   $_ := set $result "value" "desired" -}}
{{- end -}}
{{- $result.value -}}
{{- end -}}

할당과 초기화의 예 (주의 깊게 살펴보세요!)

{{- define "exampleTemplate" -}}
{{- $someVar := "default" -}}
{{- if true -}}
{{-   $someVar = "desired" -}}
{{- end -}}
{{- $someVar -}}
{{- end -}}

템플릿을 사용하는 예:

{{- define "exampleTemplate" -}}
foo:
  bar:
   baz: bat
{{- end -}}

그리고 위의 내용을 변수와 구성에 담아내기:

{{- $fooVar := include "exampleTemplate" . | fromYaml -}}
{{- $barVar := merge $.Values.global.some.config $fooVar -}}
config:
{{ $barVar }}

템플릿 구성 파일

이 차트들은 클라우드 네이티브 GitLab 컨테이너를 사용합니다. 이러한 컨테이너는 ERBgomplate을 지원합니다.

지침:

  1. 템플릿 파일은 ConfigMap 내에서 사용합니다 (예: gitlab.yml.erb, config.toml.tpl)
    • 항목들은 템플릿으로 처리되기 위해 예상되는 확장자를 사용해야 합니다.
  2. 템플릿을 사용하여 Secret 콘텐츠를 마운트된 파일 위치로 채우세요. (예: GitLab Pages config)
  3. ERB(.erb)는 실행 중에 Ruby를 사용하는 모든 컨테이너에 사용할 수 있습니다
  4. gomplate(.tpl)는 모든 컨테이너에 사용할 수 있습니다.

ERB 사용:

표준 ERB를 사용하며, jsonyaml 모듈이 미리로드되었을 것으로 예상할 수 있습니다.

gomplate 사용:

우리는 gomplate을 컨테이너 내에서 Ruby의 크기와 표면적을 줄이기 위해 사용합니다. 또한 {% %}로 대체되는 대체 구분기호를 사용하여 gomplate 문법을 설정합니다. 이것은 Helm이 사용하는 {{ }}와 겹치지 않도록 합니다.

민감한 내용을 템플릿팅하기

비밀은 부적절하게 인코딩되거나 인용되지 않으면 YAML을 잘못하여 유효하지 않은 문자를 포함할 수 있습니다. 특히 복잡한 암호의 경우, 이러한 문자열이 다양한 구성 형식에 어떻게 추가되는지 주의해야 합니다.

지침:

  1. ERB / Gomplate 출력에서는 인용 부호로 둘러싸지 않고 인용합니다.
  2. 가능한 경우 형식별 인코더를 사용합니다.
    • 렌더링된 YAML에는 JSON 문자열을 사용합니다. YAML은 JSON의 상위 집합이기 때문입니다.
    • 렌더링된 TOML에는 JSON 문자열을 사용합니다. 왜냐하면 TOML 문자열은 유사하게 이스케이프하기 때문입니다.
  3. 따옴표로 묶인 문자열 안에 있는 따옴표로 묶인 문자열과 같은 복잡성에 주의하세요.

암호 인코딩 예제

Gitaly의 클라이언트 비밀 토큰을 예로 들어보겠습니다. 이 값인 gitaly_token은 YAML과 TOML에 템플릿으로 사용됩니다.

예를 들어 my"$pec!@l"p#assword%'를 사용해봅시다:

# YAML
gitaly:
  token: "<%= File.read('gitaly_token').strip =>"

# TOML
[auth]
token = "<%= File.read('gitaly_token').strip %>"

이는 유효하지 않은 YAML과 유효하지 않은 TOML로 렌더링됩니다.

# YAML
gitaly:
  token: "my"$pec!@l"p#assword%'"

(<unknown>): did not find expected key while parsing a block mapping at line 3 column 3

[auth]
token = "my"$pec!@l"p#assword%'"

Error on line 2: Expected Comment, Newline, Whitespace, or end of input but "$" found.

이를 <%= File.read('gitaly_token').strip.to_json %>으로 변경하면 유효한 YAML 및 TOML 형식으로 결과가 나옵니다. <% %> 바깥의 "가 삭제된 것에 주목하십시오.

gitaly:
  token: "my\"$pec!@l\"p#assword%'"

이것은 gomplate로도 수행될 수 있습니다: {% file.Read "gitaly_token" | strings.TrimSpace | data.ToJSON %}

gitaly:
  # gomplate
  token: {% file.Read "./token" | strings.TrimSpace | data.ToJSON %}
  # ERB
  token: <%= File.read('gitaly_token').strip.to_json %>

템플릿 차트 참고 사항 (NOTES.txt)

Helm의 차트 참고 사항 기능은 차트 설치 및 업그레이드 후에 유용한 정보 및 후속 지침을 제공합니다.

이러한 참고 사항은 templates/NOTES.txt에 배치됩니다.

이러한 참고 사항을 처리할 때 스타일에 관해 몇 가지 주의할 점이 있습니다. 이는 출력물이 가독성 있고 실행 가능하도록 하는데 중요합니다.

참고 사항 카테고리 선택

WARNINGNOTICE 두 가지 카테고리는 참고 사항 출력의 각 유형을 나타냅니다.

  • WARNING은 설치를 최적화하기 위해 추가 조치가 필요함을 나타냅니다.
  • NOTICE는 추가 조치가 필요하지 않은 중요한 알림을 강조합니다.

NOTES.txt의 각 항목은 위의 두 카테고리 중 하나로 시작해야 합니다. 예를 들어:

{{- if eq true .Values.some.setting }}
{{ $WARNING }}
This message is a warning.
{{- end }}

{{- if eq true .Values.some.other.setting }}
{{ $NOTICE }}
This message is a notice.
{{- end }}

이 예에서는 출력물의 각 항목 사이의 일관된 제목과 간격을 보장하는 파일 맨 위에 포함된 두 사전 정의된 변수 중 하나를 사용합니다.