GitLabバージョンアップにおける問題対応 前編

カバー

[!] この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

私たちのチームでは、ソースコードの管理にGitLabを使用しています。
GitLabはWeb型のGitリポジトリマネージャーで、OSSとして提供されています。主な機能はIssue管理、バージョン管理、コードレビュー、CI/CD等であり、ソフトウェア開発に必要なものが一通り揃っています。
このGitLabを運用している中で、

  • UIの動作が重い
  • サイズの大きなWikiを閲覧・編集・保存しようとするとHTTPの503エラーが出る

という性能に関する問い合わせが多くなりました。
GitLabのログを調査した結果、これらの問題は利用者の増加や1つ1つのページサイズの肥大化にGitLabの処理能力が追いついていないことが原因であると推察しました。そのため、これらの問題解決を目的としてGitLabのシステム構成の変更を行うことになりました。
また、以前、Mattemrostのバージョンアップを実施した際は顕著な性能の改善が見られました(Mattermostバージョンアップにおける問題対応)。この実績を踏まえて、バージョンアップを同時に行うことにしました。
この記事では、GitLabのバージョンアップ時に起きた問題の対応を通して得た知見を紹介します。

バージョンアップ

私たちの環境では、GitLabをサーバー上で動作するアプリ(以下、GitLabと表記)とDB(PostgreSQL)、メッセージキューイング・セッション管理・キャッシュ用DB(Redis)により構成しています。
バージョンアップ前後のGitLab・PostgreSQL・Redisのバージョンを以下に記載します。
GitLabは有償版と無償版がありますが、無償版のCommunity Editionを使用しています。

対象バージョンアップ前バージョンアップ後
GitLab12.0.313.11.7
PostgreSQL1012
Redis3.2.86.2.4

システム構成の変更

私たちは、GitLabをOpenStack上で構築したKubernetes(以下、k8sと表記)内で運用しています。
※k8sのバージョンは1.10です。
今回はGitLabの性能改善のため、以下の方針に沿って、これまでk8s内でPodとして動かしていたDBを専用インスタンス(Trove1)として動かすように構成を変更することにしました。

  • k8s内のトラフィック減少
  • DBとk8s内のPodが互いに受ける、負荷などの影響の回避
  • NFS2からBlock3へ、DBのマウント先変更によるディスクI/O性能の向上

バージョンアップ前

system-before

バージョンアップ後

system-after

バージョンアップ手順

GitLabをバージョンアップする際は、GitLabの公式サイトが指定する順番に沿って、以下のように段階的にバージョンを上げていく必要があります。

12.0.3 -> 12.0.12 -> 12.1.17 -> 12.10.14 -> 13.0.14 -> 13.1.11 -> 13.8.8 -> 13.11.7

私たちの環境では、バージョンを上げる用途のマシンを用意して、その中にDocker環境を構築して利用することにより、バージョンアップ作業にかかる時間を10時間から8時間に短縮しました(下図のバージョンアップ環境)。 versionup.png

バージョンアップは以下の流れで進めます。③~⑤はv12.0.3からv13.11.7に上がるまで繰り返し実施します。
①本番環境でバックアップ取得
②バージョンアップ環境でv12.0.3のGitLabのコンテナを起動し、①のバックアップからリストア
③バージョンアップ環境でGitLabのコンポーネント停止
④バージョンアップ環境でGitLabのコンテナ削除
⑤バージョンアップ環境でv12.0.12のGitLabのコンテナを起動し、マイグレーション完了を待つ
⑥バージョンアップ環境でバックアップ取得
⑦v13.11.7のイメージをビルドし、Troveの接続情報を記載したgitlab.rbを配置してコンテナ起動
⑧本番環境で⑥のバックアップからリストア

以下はDocker Composeで読み込むymlファイルの例です。このようにデータ領域をホストにマウントすることで、コンテナを削除してもデータを失うことなく連続してマイグレーションできます。このファイルは現バージョン(v12.0.3)から、新しいバージョン(v13.11.7)に上げるまでの間に経由するバージョンの数だけ用意します。
なお、ldap_configの部分は、社内のLdap設定を反映させるために入れています。

version: '2'
services:
        gitlab:
                image: gitlab/gitlab-ce:12.0.3-ce.0
                environment:
                        TZ: "Asia/Tokyo"
                        http_proxy: ''
                ports:
                        - "80:80"
                volumes:
                        - /mnt/gitlab/config/ldap_config/:/etc/gitlab/
                        - /mnt/gitlab/data/:/var/opt/gitlab/

以上がGitLabのバージョンアップ手順になります。これらの手順の中で重要な部分が以下となりますので、それぞれ説明します。

  • GitLabバックアップ取得
  • リストア
  • GitLabバージョンアップ
  • GitLabイメージビルド
  • 設定ファイル変更

GitLabバックアップ取得(本番環境→バージョンアップ環境)

GitLabのバックアップを取得します。
使用するコマンドはGitLabのバージョンによって異なります。これはGitLabバックアップ取得(バージョンアップ環境→本番環境)でも同様です。詳細はGitLabの公式サイトをご参照ください。

# gitlab-rake gitlab:backup:create

リストア

空の状態で起動させたGitLabにデータをリストアします。

  • 一部コンポーネント(DBと接続するもの)を停止します。
# gitlab-ctl stop puma
# gitlab-ctl stop sidekiq
  • バックアップファイルからリストアします。
# gitlab-backup restore BACKUP="バックアップファイル名"
  • キャッシュをクリアします。
# gitlab-rake cache:clear
  • 全てのコンポーネントを再起動します。
# gitlab-ctl restart

GitLabバージョンアップ

バージョンアップは以下の手順をv13.11.7に上がるまで繰り返します。

  • GitLabのコンポーネントを停止します。
# gitlab-ctl stop
  • GitLab(現在動いているバージョン)のコンテナを停止します。
$ docker-compose -f /home/yml_dir/docker-compose_12.0.3.yml down
  • GitLab(新しく起動するバージョン)のコンテナを起動します。
$ docker-compose -f /home/yml_dir/docker-compose_12.0.12.yml up -d
  • コンテナの状態を確認します。コンテナ起動時にSTATUS部分が(healthy)になっているか確認します。
$ docker ps -a
  • GitLabのコンポーネントの状態を確認します。すべてのコンポーネントがrunになっているか確認します。
# gitlab-ctl status
  • マイグレーションが正常に終了したか確認します。出力結果のすべてのStatus列がupになっていることを確認します。
# gitlab-rake db:migrate:status
  • 全体的な正常性確認を行います。エラーメッセージが出ていないか確認します。SANITIZE=trueは、プロジェクト毎の情報を出力させないときに使用します。
# gitlab-rake gitlab:check SANITIZE=true

GitLabバックアップ取得(バージョンアップ環境→本番環境)

GitLabのバックアップを取得します。

# gitlab-backup create SKIP=registry

SKIP=registryの部分は、エラー回避のために入れているオプションであり、registry機能を使用する設定の入っていないGitLabでバックアップを取得し、registry機能を使用するように設定したGitLabにリストアする場合は必要になります。registry機能はGitLabの提供するサービスの1つで、パッケージやコンテナイメージを保管する機能を持っています。このオプションを指定することになった経緯は次回の記事で扱います。

GitLabイメージビルド

v13.11.7のイメージをビルドします。

docker build -t gitlab-ce:13.11.7 /home/dockerfile_dir/Dockerfile

v12.0.3からv13.8.8までのバージョンについては、Docker環境でdocker pullを使ってDockerHubからダウンロードするイメージを使うため、イメージビルドするのはソースファイルの修正が必要なv13.11.7のみです。修正内容の詳細は次回の記事で扱います。
なお、v13.8.8でもソースファイルの修正が必要ですが、こちらはDockerHubからダウンロードしたイメージを元にコンテナを起動し、手動でソースファイルを修正した後にdocker commitを実行する方法でイメージを作成します。v13.8.8のソースファイルの修正内容についてはリポジトリのマイグレーションをご参照ください。
以下は使用するDockerfileの例です。

FROM docker.io/gitlab/gitlab-ce:13.11.7-ce.0

ENV http_proxy=http://proxy-example.com/
ENV https_proxy=http://proxy-example.com/

RUN apt-get update -q && \
    apt-get install -yq patch language-pack-ja-base language-pack-ja && \
    update-locale LANG=ja_JP.UTF-8 LANGUAGE=ja_JP:ja

ENV LANG ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
ENV LC_CTYPE ja_JP.UTF-8

RUN unlink /etc/localtime && \
    ln -s /usr/share/zoneinfo/Japan /etc/localtime

RUN apt-get clean

#patch main.3987bf13.chunk.js line 20
RUN /usr/bin/sed -i -e 's/totalMS>=m/totalMS>=9e3/' /opt/gitlab/embedded/service/gitlab-rails/public/assets/webpack/main.3987bf13.chunk.js && \
rm -f /opt/gitlab/embedded/service/gitlab-rails/public/assets/webpack/main.3987bf13.chunk.js.gz

設定ファイル変更

設定ファイル(gitlab.rb)を修正します。
これまでとは異なりDBにTroveを使用するため、コンテナ名で指定していた部分にTroveインスタンスのIPアドレスを指定します。

gitlab_rails['db_host'] = "192.168.1.2"

バージョンアップ時に起きた問題対応

ここではバージョンアップの検証を行う中で、私たちが直面した問題とその対応策を解説します。

リポジトリのマイグレーション

まずHashed storageについて説明します。
Hashed storageはGitLabのv10.0から導入されたもので、v13.0ではデフォルトで有効化されるようになりました。プロジェクトIDのハッシュ値に応じてディレクトリーを作成し、プロジェクト毎に管理するGitファイルを格納する仕組みです。v13.0以降、バージョンアップのいずれかのタイミングで、それまで使われていたLegacy storageからHashed storageへのリポジトリのマイグレーションが自動で行われます。
私たちの環境ではv13.8.8に上げたとき、GitLabのダッシュボードの管理者画面にある、監視→バックグラウンドジョブの「デッド」に以下のようなメッセージが現れました。

Projects::HashedStorage::RepositoryInUseError: Target repository 'testowner/testrepo' cannot be made read-only: Repository already read-only

エラーの内容は、リポジトリをマイグレーションしようとした時に、読み取りモードに切り替えることができないというものです。より具体的には、GitLabのprojectsテーブルのカラム(repository_read_only)をtrueにしようとして、すでにtrueの場合にこのエラーが発生します。これはGitLabのバグが原因であり、ソースファイル(migrate_repository_service.rb)の修正が必要です。以下のパッチをあてることにより、リポジトリをマイグレーションする際のエラーが出なくなりました。+で始まる行を追加しています。

 def try_to_set_repository_read_only!
         project.set_repository_read_only!
       rescue Project::RepositoryReadOnlyError => err
+        if err.message == "Repository already read-only"
+          return
+        end
         migration_error = "Target repository '#{old_disk_path}' cannot be made read-only: #{err.message}"
         logger.error migration_error

         raise RepositoryInUseError, migration_error
       end

GitLabのスニペットで表示されないものがある

スニペットはサイズの小さなソースコードやテキストファイルを保存し、他のユーザーと共有できる機能です。ユーザー単位やプロジェクト単位で作成することが可能です。 snippet_sample.png

v12.1.17からv12.10.14に上げた際、属性がプロジェクトのメンバー以外からもアクセス可能なPublicになっているにもかかわらず、システム管理者権限を持つユーザーの画面からデフォルトで表示できないスニペットが複数見つかりました。
下の図はシステム管理者権限を持つユーザーでログインし、スニペットを一覧表示させたものです。2つのスニペットが確認できますが、これらはユーザー単位で作成したものです。v12.1.17までは、この画面にプロジェクト単位で作成したスニペットも表示されていたのですが、v12.10.14に上げたタイミングで見えなくなりました。 snippet_sample2.png

検索欄にスニペットの名前を入力すると表示されますが、管理者画面からスニペットを一覧化したいときには不便です。 snippet_sample3.png

これはGitLabの仕様変更が原因であり、ソースファイル(snippets_controller.rb)の関数(SnippetsFinder)に引数が追加されています。

v12.1.17
@snippets = SnippetsFinder.new(current_user).execute
v12.10.14
@snippets = SnippetsFinder.new(current_user, explore: true)

ソースファイル内のコメントを読むと、プロジェクト毎のスニペットを表示することはあまり重要でないとの判断から、ユーザー単位で作成したスニペットのみ表示するように仕様を変更したようです。
最終的には、スニペットの仕様が変更されたことについては対応せずにリリースして、要望があれば改修を検討することにしました。

おわりに

今回はGitLabのバージョンアップの流れと問題対応について解説しました。この記事は前後編の前編であり、次回も引き続きGitLabバージョンアップの際に起きた問題対応について解説します。

Footnotes

  1. OpenStackのDBaaSであるTroveの機能を使用しています。Troveについては下記の記事をご覧ください。
    OpenStack の DBaaS(Trove)導入 - Trove 導入入門編

  2. NFSはOpenStackが提供する共有ファイルシステム(Manila)を使用しています。

  3. BlockはOpenStackが提供するブロックストレージサービス(Cinder)を使用しています。
    Cinderについては下記の記事をご覧ください。
    OpenStack環境でBlockストレージを増設する - 基礎知識編
    OpenStack環境でBlockストレージを増設する - 増設計画編
    OpenStack環境でBlockストレージを増設する - 増設作業編


TOP
アルファロゴ 株式会社アルファシステムズは、ITサービス事業を展開しています。このブログでは、技術的な取り組みを紹介しています。X(旧Twitter)で更新通知をしています。