GitlabをKubernetes環境にデプロイしてみた

# はじめに

以前に Gitbucketをk8s環境にデプロイして利用してきましたが、より本格的なGitlab(sameersbn/docker-gitlab)のコンテナを稼動させてみます。

デフォルト状態のGitLabはログインしないとリポジトリを自由に閲覧することができません。
Publicなリポジトリ、あるいはExploreページやユーザーページのURLを直接指定すれば、閲覧することはできますが、手掛かりがないとトップページから遷移できません。

この点、Gitbucketは中小規模での利用やコードを公開する用途では、そのままでも手軽で十分な機能があると思います。

:::note
この記事はv15.11.13までの導入についてまとめています。
v15.11.13からv16.7.3へのバージョンアップについては別記事にまとめています。

* [Kubernetes環境のsameersbn版GitLab 15.11.13を16系列の最新版(16.7.3)にしてみた(ほぼ解決)](https://qiita.com/YasuhiroABE/items/58e1e4b0f600d29e6166)
:::

## 参考資料

* [Gitbucketをkubernetesにデプロイしてみた](https://qiita.com/YasuhiroABE/items/e5bda8606c3923b42038)
* [https://github.com/sameersbn/docker-gitlab](https://github.com/sameersbn/docker-gitlab)
* [GitLab Upgrade Path](https://gitlab-com.gitlab.io/support/toolbox/upgrade-path/)
* [GitLab Upgrade Guide](https://docs.gitlab.com/update/)

## 環境

* Kubernetes - v1.25.6 (Kubespray v2.21.0)
* Rook/Ceph v1.9.13 (ceph version 16.2.10)
* Ingress (Webアクセス用。Serviceでtype: LoadBalancerはSSHでのみ利用)
* OpenLDAP (ユーザー認証用)

GitlabのCE版はLDAPに接続するだけで、フィルターが有効に使えないのが少し残念なポイントです。

## 方針

[sameersbn/docker-gitlab](https://github.com/sameersbn/docker-gitlab)は、docker-compose.yamlファイルだけでなく、kuberentesで稼動させるためのYAMLファイルが含まれています。

```bash:準備作業
$ git clone https://github.com/sameersbn/docker-gitlab.git
$ cd docker-gitlab
$ git checkout refs/tags/15.2.2 -b t_15.2.2
```

kubernetes/ディレクトリにあるファイルは、PVCの定義が抜けている点と、gitlab-svc.yamlでtype: LoadBalancerが指定されていますが、これは使わずにIngress経由でcontext-rootを変更し、/gitlab/に来たリクエストを振ります。

## 考慮点

負荷分散させるためのgitalyのクラスター化や、Object Storageの利用などは行なっていません。
そのため、redis/postgresqlはクラスター化させることは比較的容易だと思いますが、全てのインスタンス(Pod)は1つずつ動かしています。

これらがSPoF(単一障害点)になることは認識しておく必要があります。

## YAMLファイル

変更した部分を加えて、YAMLファイルの全体を紹介していきます。

### PVCファイル

Rook/Cephを利用するためのPVCを準備していきます。
あらかじめ準備されているredis-rc.ymlでは/var/lib/redisを保護していますが、利用するイメージは/dataが永続化対象なので、このpathを含めて変更しています。

```yaml:redis-pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: redis-data-pvc
  namespace: gitlab
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 10Gi
  storageClassName: rook-ceph-block
```

```yaml:postgresql-pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pg-data-pvc
  namespace: gitlab
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 200Gi
  storageClassName: rook-ceph-block
```

```yaml:gitlab-pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: gitlab-data-pvc
  namespace: gitlab
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 100Gi
  storageClassName: rook-ceph-block
```

### SVCファイル

Service用のYAMLファイルで変更を加えたのは、gitlab-svc.ymlだけです。

```yaml:gitlab-svc.yml
apiVersion: v1
kind: Service
metadata:
  name: gitlab
  namespace: gitlab
  labels:
    name: gitlab
spec:
  type: LoadBalancer
  ports:
    - name: http
      port: 80
      targetPort: 80
    - name: ssh
      port: 10022
      targetPort: 22
  selector:
    name: gitlab
```

外部からのアクセスを22番ポートで受けることはできないので、ssh経由のアクセスは10022番ポートで待ち受けます。
フロントエンドのWebサーバー上で、10022番ポートのアクセスをこのLoadBalancerで割り当てられたEXTERNAL-IPに接続させます。

冒頭で説明したように、80番ポートへはEXTERNAL-IPは使わずにIngressからsvc/gitlabサービス(gitlab.gitlab.svc.cluster.local)にProxy接続させます。

### RCファイル

Deploymentオブジェクトではなく、ReplicationControllerオブジェクトを使うことは最近のKubernetesではありませんが、書き換えるのも面倒なのでこのまま書き換えていきます。

主な変更点は、定義したPVCを利用するようにvolumeMounts/volumesを変更する点と、DB接続用のパスワードやGitlabの各種設定(特にLDAP設定)を加えていくことになります。

```yaml:redis-rc.yml
apiVersion: v1
kind: ReplicationController
metadata:
  name: redis
  namespace: gitlab
spec:
  replicas: 1
  selector:
    name: redis
  template:
    metadata:
      name: redis
      labels:
        name: redis
    spec:
      containers:
      - name: redis
        image: redis:6.2.7
        ports:
        - name: redis
          containerPort: 6379
        volumeMounts:
        - mountPath: /data
          name: redis-data
        livenessProbe:
          exec:
            command:
            - redis-cli
            - ping
          initialDelaySeconds: 30
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command:
            - redis-cli
            - ping
          initialDelaySeconds: 5
          timeoutSeconds: 1
      volumes:
      - name: redis-data
        persistentVolumeClaim:
          claimName: redis-data-pvc
```

```yaml:postgresql-rc.yml
apiVersion: v1
kind: ReplicationController
metadata:                    
  name: postgresql   
  namespace: gitlab 
spec:
  replicas: 1
  selector:
    name: postgresql
  template:
    metadata:
      name: postgresql
      labels:
        name: postgresql
    spec:
      containers:
      - name: postgresql
        image: sameersbn/postgresql:12-20200524
        env:
        - name: DB_USER
          value: gitlab
        - name: DB_PASS
          value: e6cb3ac870c56287
        - name: DB_NAME
          value: gitlab_production
        - name: DB_EXTENSION
          value: pg_trgm,btree_gist
        ports:
        - name: postgres
          containerPort: 5432
        volumeMounts:
        - mountPath: /var/lib/postgresql
          name: data
        livenessProbe:
          exec:
            command:
            - pg_isready
            - -h
            - localhost
            - -U
            - postgres
          initialDelaySeconds: 30
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command:
            - pg_isready
            - -h
            - localhost
            - -U
            - postgres
          initialDelaySeconds: 5
          timeoutSeconds: 1
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: pg-data-pvc
```

postgresql-rc.ymlの変更点はPVCの他に、DB_EXTENSIONにbtree_gistを追加した点とDB接続用のパスワードを設定したところです。

```yaml:gitlab-rc.yml
---                          
apiVersion: v1                           
kind: ReplicationController             
metadata:                            
  name: gitlab                    
  namespace: gitlab                               
spec:                       
  replicas: 1                                
  selector:                                   
    name: gitlab           
  template:                                                
    metadata:                                    
      name: gitlab                            
      labels:                                    
        name: gitlab                       
    spec:                                        
      containers:                      
      - name: gitlab                                     
        image: sameersbn/gitlab:15.2.2
        env:
        - name: DEBUG
          value: "false"

        - name: TZ
          value: "Asia/Tokyo"
        - name: GITLAB_TIMEZONE
          value: "Tokyo"

        - name: GITLAB_UNICORN_MEMORY_MAX
          value: "629145600"
        - name: GITLAB_HTTPS
          value: "false"
        - name: SSL_SELF_SIGNED
          value: "false"
        - name: OAUTH_ENABLED
          value: "false"

        - name: GITLAB_SECRETS_DB_KEY_BASE                 
          value: 1c10d46a57abd38c2c7957d87980fecb
        - name: GITLAB_SECRETS_SECRET_KEY_BASE
          value: e927734a7fdb0363e078ede99e4d7180
        - name: GITLAB_SECRETS_OTP_KEY_BASE
          value: 4f69440296aa7db3be56c5ea2d8f3876
                                       
        - name: GITLAB_ROOT_PASSWORD     
          value: ""       
        - name: GITLAB_ROOT_EMAIL
          value: "gitlab-admin@example.com"
        - name: GITLAB_EMAIL_REPLY_TO 
          value: "gitlab-admin@example.com"
        - name: GITLAB_EMAIL_SUBJECT_SUFFIX
          value: "[Gitlab]"
                                   
        - name: GITLAB_RELATIVE_URL_ROOT
          value: "/gitlab"   
        - name: GITLAB_HOST    
          value: "external.example.com"
        - name: GITLAB_PORT          
          value: "80"             
        - name: GITLAB_SSH_PORT                   
          value: "10022"    
                                             
        - name: GITLAB_NOTIFY_ON_BROKEN_BUILDS
          value: "false"   
        - name: GITLAB_NOTIFY_PUSHER                       
          value: "false"                
                            
        - name: GITLAB_BACKUP_SCHEDULE       
          value: daily         
        - name: GITLAB_BACKUP_TIME          
          value: 01:00                 
                                                         
        - name: DB_ADAPTER
          value: "postgresql"
        - name: DB_TYPE
          value: postgres
        - name: DB_HOST
          value: postgresql
        - name: DB_PORT
          value: "5432"
        - name: DB_USER
          value: gitlab
        - name: DB_PASS
          value: e6cb3ac870c56287
        - name: DB_NAME
          value: gitlab_production

        - name: REDIS_HOST
          value: redis                       
        - name: REDIS_PORT         
          value: "6379"  
                                                           
        - name: SMTP_ENABLED            
          value: "true" 
        - name: SMTP_DOMAIN                  
          value: "example.com"
        - name: SMTP_HOST                   
          value: "smtp.example.com"
        - name: SMTP_PORT                
          value: "25"     
        - name: SMTP_USER   
          value: ""      
        - name: SMTP_PASS             
          value: ""        
        - name: SMTP_STARTTLS
          value: "false"   
        - name: SMTP_AUTHENTICATION
          value: "plain"           
                             
        - name: IMAP_ENABLED   
          value: "false"

        - name: LDAP_ENABLED
          value: "true"                  
        - name: LDAP_LABEL
          value: "AINS LDAP"
        - name: LDAP_HOST
          value: "ldap.example.com"
        - name: LDAP_PORT  
          value: "636"
        - name: LDAP_UID   
          value: "uid"
        - name: LDAP_METHOD        
          value: "simple_tls"
        - name: LDAP_VERIFY_SSL
          value: "true"
        - name: LDAP_ACTIVE_DIRECTORY
          value: "false"
        - name: LDAP_ALLOW_USERNAME_OR_EMAIL_LOGIN
          value: "true"     
        - name: LDAP_BLOCK_AUTO_CREATED_USERS
          value: "true"
        - name: LDAP_BASE  
          value: "ou=People,ou=Proxy,dc=example,dc=com"
        - name: LDAP_USER_ATTRIBUTE_NAME
          value: "gecos"    
        - name: LDAP_USER_ATTRIBUTE_FIRSTNAME
          value: "gecos"
        - name: LDAP_USER_ATTRIBUTE_LASTNAME
          value: "uid"              
        - name: LDAP_PREVENT_LDAP_SIGN_IN                
          value: "false"

        ports:
        - name: http
          containerPort: 80
        - name: ssh
          containerPort: 22
        volumeMounts:
        - mountPath: /home/git/data
          name: data
        #livenessProbe:
        #  httpGet:
        #    path: /gitlab/
        #    port: 80
        #  initialDelaySeconds: 180
        #  timeoutSeconds: 5
        #readinessProbe:
        #  httpGet:
        #    path: /gitlab/
        #    port: 80
        #  initialDelaySeconds: 5
        #  timeoutSeconds: 1
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: gitlab-data-pvc
```

## Ingress等、他の設定について

環境変数(GITLAB_RELATIVE_URL_ROOT、GITLAB_HOST)を設定することによって、外部からは "https://external.example.com/gitlab/" をトップにしています。

またこのホストの10022番ポート経由で、sshによるclone/pushなどの操作を行ないます。

このためIngressや、境界サーバー(external.example.com/)のポートフォワードなどの設定を行ないます。
境界に配置したWebサーバー(https://external.example.com/)の設定方法などは含めていません。


### Ingressによるexternal.example.comからの接続

namespace: ingress-nginxに次のようなService(svc)オブジェクトを定義します。
このexternalName先がgitlab-svc.ymlで作成した namespace: gitlab に存在する、svc/gitlabオブジェクトを指しています。

```yaml:ingress-svc-gitlab.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: gitlab-svc
  labels:
    group: ingress-nginx
  namespace: ingress-nginx
spec:
  type: ExternalName
  externalName: gitlab.gitlab.svc.cluster.local
```

Ingress自体の設定に次のような定義を追加します。既に利用している他の定義を上書きしないように注意してください。

```yaml:ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: default
  labels:
    group: ingress-nginx
  namespace: ingress-nginx
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: gitlab-svc
            port:
              number: 80
        path: /gitlab
        pathType: Prefix
      ...  ## 他の backend: 定義が続く
```

ingress.yamlで定義している .spec.rules.http.paths.backend.service.name に指定しているのが、ingress-svc-gitlab.yaml で定義した namespace: ingress-nginx の svc/gitlab-svcオブジェクト を指しています。


### 10022番ポートのポートフォワード

このサーバーはCentOS 8 Streamである点と、Ansibleを使っている点が特徴的です。

```yaml:myrole/tasks/main.yml
- name: firewalld enablse the masquerade service for external
  firewalld:
    zone: external
    masquerade: yes
    permanent: true
    state: enabled

- name: firewalld external config for the gitlab ssh port
  firewalld:
    zone: external
    rich_rule: rule family=ipv4 forward-port port=10022 protocol=tcp to-port=10022 to-addr=192.168.1.22
    permanent: yes
    immediate: yes
    state: enabled
```

### Ubuntuを利用する場合

Ubuntuを使っていて、ufwを利用する場合には少し複雑になります。
ただufwは利用せずに socat を使った方が安定しているので、こちらを利用しています。

```bash:/usr/local/sbin/check-gitlab
#!/bin/bash

TARGET="10022"

ps auxww|grep "${TARGET}" | grep -v grep > /dev/null
rc="$?"

if test "${rc}" != "0" ; then
    nohup socat tcp4-listen:10022,reuseaddr,fork TCP:192.168.1.22:10022 &
fi
```

再起動時や異常終了時の自動起動のためにcronを使ってスケジュールしています。

```crontab:rootのcrontabからの抜粋
#Ansible: check-gitlab
2 * * * * /usr/local/sbin/check-gitlab
```

# アップグレードについて

現状は15.3.1や15.3.3が稼動しています。15.2.2を含めて15系列の最新版(15.11.13)にするためには、まず15.4.6にアップグレードする必要があります。

GitLabではアップグレードパスが提供されているので、公式サイトの情報確認が必須です。

いまのところ ``15.0.5 > 15.1.6 (for GitLab instances with multiple web nodes) > 15.4.6 > 15.11.13.`` とされていて、15.11.13 からは、16.1.5→16.3.6→16.6.1の順で最新版に更新できるようです。

ただ最新版への更新はバグが入り込むことが怖いので、15.11.13でしばらく様子をみたいと思います。

バックアップはPod上の/home/git/data/backups/以下にあるので紛失しないように手元にコピーを置いておくのがお勧めです。

テスト系があるので、gitlab-rc.yamlのgitlabのバージョンを15.4.6に変更して適用してみます。

```diff:gitlab-rc.yamlの差分
diff --git a/gitlab/yaml/gitlab-rc.yml b/gitlab/yaml/gitlab-rc.yml
index f489a43..7b42ad1 100644
--- a/gitlab/yaml/gitlab-rc.yml
+++ b/gitlab/yaml/gitlab-rc.yml
@@ -16,7 +16,7 @@ spec:
     spec:
       containers:
       - name: gitlab
-        image: sameersbn/gitlab:15.3.1
+        image: sameersbn/gitlab:15.4.6
         env:
         - name: DEBUG
           value: "false"
```

これを適用しても自動的にはPodはリスタートしないため、手動でPodを削除します。

新しいバージョンでgitlabのPodが起動し、ログを確認すると``Recompiling assets (relative_url in use), this could take a while...`` の部分からしばらく処理が止まります。

```text:
success Packages successfully patched.
Done in 41.60s.
yarn add v1.22.19
[1/5] Validating package.json...
[2/5] Resolving packages...
[3/5] Fetching packages...
[4/5] Linking dependencies...
warning "@gitlab/eslint-plugin > eslint-plugin-jest > @typescript-eslint/experimental-utils > @typescript-eslint/typescript-estree > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta".
warning "@graphql-eslint/eslint-plugin > graphql-config > @endemolshinegroup/cosmiconfig-typescript-loader > ts-node@9.1.1" has unmet peer dependency "typescript@>=2.7".
warning Workspaces can only be enabled in private projects.
[5/5] Building fresh packages...
warning "ajv" is already in "devDependencies". Please remove existing entry first before adding it to "dependencies".
warning Workspaces can only be enabled in private projects.
success Saved 0 new dependencies.
$ node ./scripts/frontend/postinstall.js
success Dependency postinstall check passed.
success Packages successfully patched.
Done in 3.24s.
Recompiling assets (relative_url in use), this could take a while...
```

かなり時間がかかりますが、辛抱強く待っていると処理が進みWebUIからログインできるようになります。

まだバックグラウンドで更新処理が進んでいる可能性があるので、次のバージョンに更新する処理を急がずに、Admin画面から更新処理が終わっていることを確認します。

rootでログインしてから、Admin → Monitoring → Background Migrations と進み、全ての処理がFinish状態になっていることを確認します。

ここまで確認してから繰り返し次のアップグレードに進みます。


以上