オンプレGitLabでgit+CI+npmサーバー2024 の履歴(No.10)

更新


プログラミング/svelte

自前サーバーで GitLab を動かしてプライベートな npm プロジェクトなどを置きたい

GitLab は、

  • GitHub のように使える git 利用開発支援機能
  • テストやビルドやデプロイを自動化する CI サーバー機能
  • npm パッケージサーバー機能

など、さまざまな機能を兼ね備えるフリーソフト。

自前のサーバーにインストールして動かせる。

GitLab を動かすには 4GB 以上のメモリが必要で、しばらく前だと二の足を踏んでいたのだけれど今なら軽いはず。

Docker を使ってサクッと動かす。(動かせるといいな)

今のところ、

  • docker で GitLab を動かす
  • ホストの apache2 をプロキシサーバーとして外からの接続を docker との間で中継する
  • docker で GitLab Runner を動かす
  • GitLab に Runner を登録する(shell モード)
  • CI/CD で成果物を .zip としてダウンロード可能にする
  • GitLab に Runner を登録する(docker モード)
  • GitLab の npm パッケージサーバーへパッケージを Deploy する(手動)
  • GitLab の npm パッケージサーバーから npm install でパッケージを使う
  • GitLab の npm パッケージサーバーへパッケージを Deploy する(CI スクリプト)

までをなんとか実現しました。

  • リリースページから CI で作った成果物をダウンロードさせる

はまだこれからです(すぐにやりたくなるかどうか不明です)。

目次

GitLab サーバーを docker compose で動かす

https://gitlab-docs.creationline.com/ee/install/docker.html#install-gitlab-using-docker-compose

を見ながら docker イメージで動作するよう設定する。

  • ホストサーバーには docker 環境があって、自分が管理者である
    • gitlab は docker 内で動かす
  • ホストサーバーには apache2 が https://myserver.com/ への通信を受け付ける状況になっている
  • https://myserver.com/gitlab/ というアドレスへの通信を docker 内の gitlab サーバーへ繋ぐ

という形で GitLab を動かします。

LANG:console
$ cd ~
$ mkdir gitlab
$ cd gitlab
$ echo GITLAB_HOME=`pwd` > .env
$ cat <<"EOS" > docker-compose.yml
services:
  web:
    image: 'gitlab/gitlab-ce:latest'
    restart: always
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://myserver.com/gitlab'
        nginx['listen_port'] = 80
        nginx['listen_https'] = false
        gitlab_rails['gitlab_shell_ssh_port'] = 2224
    ports:
      - '8980:80'
    volumes:
      - '$GITLAB_HOME/etc:/etc/gitlab'
      - '$GITLAB_HOME/log:/var/log/gitlab'
      - '$GITLAB_HOME/data:/var/opt/gitlab'
    shm_size: '256m'
EOS
$ docker compose up -d
[+] Running 10/10
 ✔ web Pulled                               58.2s 
   ✔ 857cc8cb19c0 Pull complete              2.1s 
   ✔ 28812802a434 Pull complete              2.1s 
   ✔ 54e2e989e54c Pull complete              3.0s 
   ✔ abb7892b26dc Pull complete              3.0s 
   ✔ e9d667f5a8c1 Pull complete              3.0s 
   ✔ a8891519352d Pull complete              3.1s 
   ✔ 8b624a00a604 Pull complete              3.1s 
   ✔ 0cf3370d74b6 Pull complete              3.1s 
   ✔ 3253094bd895 Pull complete             55.9s 
[+] Running 2/2
 ✔ Network gitlab_default  Created           0.0s 
 ✔ Container gitlab-web-1  Started           0.7s 

これで docker 内で GitLab サーバーが立ち上がる。

全体としての注意

上記のようにして動かすと、etc/ フォルダに etc/gitlab.rb というのができて、 これを書き換えれば設定を変えられる。

設定項目についてはこちらに説明がある。
https://gitlab-docs.creationline.com/omnibus/settings/configuration.html

また、ファイルの冒頭には以下の注意書きがある。

## GitLab configuration settings
##! This file is generated during initial installation and **is not** modified
##! during upgrades.
##! Check out the latest version of this file to know about the different
##! settings that can be configured, when they were introduced and why:
##! https://gitlab.com/gitlab-org/omnibus-gitlab/blame/master/files/gitlab-config-template/gitlab.rb.template
 
##! Locally, the complete template corresponding to the installed version can be found at:
##! /opt/gitlab/etc/gitlab.rb.template
 
##! You can run `gitlab-ctl diff-config` to compare the contents of the current gitlab.rb with
##! the gitlab.rb.template from the currently running version.
 
##! You can run `gitlab-ctl show-config` to display the configuration that will be generated by
##! running `gitlab-ctl reconfigure`
 
##! In general, the values specified here should reflect what the default value of the attribute will be.
##! There are instances where this behavior is not possible or desired. For example, when providing passwords,
##! or connecting to third party services.
##! In those instances, we endeavour to provide an example configuration.
 
## GitLab URL
##! URL on which GitLab will be reachable.
##! For more details on configuring external_url see:
##! https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-the-external-url-for-gitlab
##!
##! Note: During installation/upgrades, the value of the environment variable
##! EXTERNAL_URL will be used to populate/replace this value.
##! On AWS EC2 instances, we also attempt to fetch the public hostname/IP
##! address from AWS. For more details, see:
##! https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html
# external_url 'GENERATED_EXTERNAL_URL'
 
## Roles for multi-instance GitLab
##! The default is to have no roles enabled, which results in GitLab running as an all-in-one instance.
##! Options:
##!   redis_sentinel_role redis_master_role redis_replica_role geo_primary_role geo_secondary_role
##!   postgres_role consul_role application_role monitoring_role
##! For more details on each role, see:
##! https://docs.gitlab.com/omnibus/roles/index.html#roles
##!
# roles ['redis_sentinel_role', 'redis_master_role']
 
## Legend
##! The following notations at the beginning of each line may be used to
##! differentiate between components of this file and to easily select them using
##! a regex.
##! ## Titles, subtitles etc
##! ##! More information - Description, Docs, Links, Issues etc.
##! Configuration settings have a single # followed by a single space at the
##! beginning; Remove them to enable the setting.
 
##! **Configuration settings below are optional.**

一方、例えば data/nginx/conf/gitlab-http.conf に nginx 関連の設定ファイルが見つかるのだけれど、 こちらの設定ファイルはサーバーを立ち上げ直すと gitlab.rb の内容を元に自動的に生成されるものなので、 手で書きかえてはいけない。

ちゃんと冒頭に

# This file is managed by gitlab-ctl. Manual changes will be
# erased! To change the contents below, edit /etc/gitlab/gitlab.rb
# and run `sudo gitlab-ctl reconfigure`.

などと書かれている。

etc/gitlab.rb を書き換えてしまうと設定情報が docker-compose.yml と gitlab.rb とに分かれてしまうので、どちらか一方にまとめた方がいいのかもしれない。

単純な見やすさからすると docker-compose.yml に書く方が良さそうなのだけれど、 gitlab-ctl diff-config を使うなら gitlab.rb でもいいのかもしれない。

docker コンテナの外から確認するなら:

LANG:console
$ docker compose exec web gitlab-ctl diff-config

ポートの指定

外からの接続を受ける apache2 と同じマシンに置くのであれば docker-compose.yml の

     ports:
       - '8980:80'

のところは

     ports:
       - '127.0.0.1:8980:80'

として、外からの接続が直接 docker へ行かないようにする。

docker でポートを開けると ufw などのファイアウォールで許可してなくても 外向きにポートが開いてしまうので十分注意する。

https を名乗りつつ http で待ち受ける

gitlab の設定で

      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://myserver.com/gitlab'
        nginx['listen_port'] = 80
        nginx['listen_https'] = false

としている理由は以下の通り。

この docker イメージは external_url の設定を見て、内部の nginx で 80 番で http を待つか、443 番で https を待つかを決めている。

「どちらか片方しか有効にならない」というのが重要な点。

今実現したい構成では、外部から見た gitlab サーバーのアドレスは https://myserver.com/gitlab で https 接続になるのだけれど、その暗号化通信は間に入る apache2 が受け持つので docker 内の nginx への接続は http になる。

external_url を https としてしまうと gitlab docker は nginx を 443 番で https を待つように設定してしまい 80 番の http では繋がらなくなってしまう。

そこで 80 番で http を待つように設定しているのがこの部分だ。

サーバーに繋ぎに行って「接続をリセットされました」みたいになるときは、

LANG:console
$ sudo less data/nginx/conf/gitlab-http.conf

として gitlab 関連の nginx の設定を確かめるといい。

(これに気付くのにかなり時間がかかりました・・・)

GITLAB_OMNIBUS_CONFIG で指定可能なその他の設定

GITLAB_OMNIBUS_CONFIG では

LANG:console
$ sudo less etc/gitlab.rb 

として表示される設定項目に値を指定できるので、 「こんなことできないのかな」と思ったときにはじっくり読むといい。

apache2 をプロキシサーバーとして接続を中継させる

ホストマシンの /etc/apache2/sites-enabled/default-ssl.conf に

<IfModule mod_proxy.c>
    ProxyRequests Off
    <Proxy *>
        Require all granted
    </Proxy>

    AllowEncodedSlashes NoDecode
    <Location /gitlab>
        Order deny,allow
        Allow from all
    </Location>
    RequestHeader set X_FORWARDED_PROTO 'https'
    ProxyPass /gitlab http://localhost:8980/gitlab
    ProxyPassReverse /gitlab http://localhost:8980/gitlab
</IfModule>

と設定して、

LANG:console
$ sudo a2enmod proxy
$ sudo service apache2 reload

とすることで apache2 がプロキシサーバーとして振る舞い、 https://myserver.com/gitlab 以下へのアクセスを http://localhost:8929/gitlab へ転送してくれる。

ここで重要なのが AllowEncodedSlashes NoDecode だ。

これをしておかないと、後々 GitLab を npm パッケージサーバーとして使おうとした際に、パッケージリポジトリへの PUT が 404 で失敗する。

https://stackoverflow.com/questions/20496963/avoid-nginx-decoding-query-parameters-on-proxy-pass-equivalent-to-allowencodeds

LANG: console
$ npm publish    # 他の設定が終わってからの話
...
npm error code E404
npm error 404 Not Found - PUT https://myserver.com/gitlab/api/v4/projects/1/packages/npm/@myscope%2fmypackage
npm error 404
npm error 404  '@myscope/mypackage@0.0.1' is not in this registry.
npm error 404
npm error 404 Note that you can also install from a
npm error 404 tarball, folder, http url, or git url.

のように、npm publish で認証は通過しているのに、そして、アップロードしようとしているのに 404 で落ちるていたらこの設定を見直すといい。

メールを送れるように設定する

GITLAB_OMNIBUS_CONFIG に

gitlab_rails['gitlab_email_from'] = 'gitlab@myserver.com'
gitlab_rails['gitlab_email_display_name'] = 'GitLab'
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.server"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = "smtp user"
gitlab_rails['smtp_password'] = "smtp password"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_tls'] = false
gitlab_rails['smtp_pool'] = false

あたりを追加する。

設定の仕方はこちらを参照:
https://gitlab-docs.creationline.com/omnibus/settings/smtp.html

root でログインする

初期設定で root という管理者ユーザーが登録されているので、 ウェブサーバーから https://myserver.com/gitlab へアクセスして root でログインする。

管理者 root の初期パスワードは gitlab の docker-compose.yml を作ったフォルダの下の etc フォルダを調べるとわかる。

LANG:console
$ sudo cat etc/initial_root_password 

普段使いのユーザーを作っておく

この root ユーザーを使って普段使いのユーザー(以下では "myname" とする)を作っておく。

自分で変更した root のパスワードを忘れてしまった場合

root のパスワードを自分で変更して、その後忘れてしまうと困ったことになる。

そんな時は、

https://blog.k-bushi.com/post/tech/environment/construct-gitlab-runner-docker/#gitlab%E3%81%B8%E3%81%AE%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3

にあるように、

LANG:console
$ docker compose exec web gitlab-rake "gitlab:password:reset"
Enter username: root
Enter password: 
Confirm password: 
Password successfully updated for user with username root.

とすればパスワードを設定しなおせるらしい。

GitLab Runner も docker で動かす

CI で実際にジョブを実行するサーバーは GitLab Runner と呼ばれる。

同時に使用する人数と比較すると今使っている物理サーバーには余裕があるので、 CI サーバーも同じ物理サーバーで動かしてしまうことにした。

先の docker-compose.yml に以下を追加する。

  runner:
    image: 'gitlab/gitlab-runner:latest'
    restart: unless-stopped
    volumes:
      - '$GITLAB_HOME/runner:/etc/gitlab-runner'
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - '8093:8093'

そして、

LANG:console
$ sudo docker compose up -d

Runner を GitLab に追加する

GitLab 画面で新しい Runner インスタンスを作成

https://myserver.com/gitlab/admin/runners

から右上の [New Instance Runner] を押す。

ここでは特別な設定をしないごく一般的な shell の動くサーバーを建てるので、

x Run untagged jobs のチェックを付けて [Create runner] とする。

docker で動かした Runner と対応付ける

次の画面の Step 1 となっているところで

$ gitlab-runner register
   --url https://myserver.com/gitlab
   --token glrt-iqghvxb-GLHJG9WsFPT6

のようなところをコピーする。

ちょっと手直しをして次の形にして Runnner を起動した docker-compose.yml のあるフォルダで実行。

$ docker compose exec runner \
   gitlab-runner register \
       --url https://myserver.com/gitlab \
       --token glrt-iqghvxb-GLHJG9WsFPT6

コマンドを起動するといくつか質問に答えることになる。

  • GitLab instance URL には指定した https://myserver.com/gitlab が入っているのでそのまま ENTER
  • Enter a name for the runner にはこの Runner を識別する名前を付ける
  • Enter an executor には shell を指定

これで Runner 一覧に正しく追加される。

ERROR: Verifying runner... failed → extra_hosts の設定が必要?

何らかの事情で Runner の docker 内部から myserver.com で apache2 の動くサーバーへ到達できない場合には(LAN 内で ローカル IP を持っている場合など)、

$ docker compose exec runner \
   gitlab-runner register \
       --url https://myserver.com/gitlab \
       --token glrt-iqghvxb-GLHJG9WsFPT6
                                                   
Enter the GitLab instance URL (for example, https://gitlab.com/):
[https://myserver.com/gitlab]: <ENTER>

ERROR: Verifying runner... failed                   runner=yFiGd5DTA status=couldn't execute POST against https://myserver.com/gitlab/api/v4/runners/verify: Post "https://myserver.com/gitlab/api/v4/runners/verify": dial tcp xx.xx.xx.xx:443: connect: connection refused
PANIC: Failed to verify the runner.            

のように connection refused というエラーが出て失敗する。

このときは docker-compose.yml 内で runner に

    extra_hosts:
      - myserver.com:192.168.0.2

のようなのを追加して https://myserver.com/gitlab でサーバーへ到達できるようにしておく必要がある。

CI 動作を試してみる

LANG: console
$ mkdir ~/gitlab-ci-hello
$ cd ~/gitlab-ci-hello
$ cat <<"EOS" > .gitlab-ci.yml 
build:
  script:
    - echo hello `pwd`
    - ls -la
EOS
$ git init
$ git add .
$ git commit -m "Initial commit"

として .gitlab-ci.yml だけをコミットした git リポジトリを作成する。

GitLab 上でブランクプロジェクトを作ってそこへコミットしてみる。

https://myserver.com/gitlab/projects/new#blank_project

そして Create Project

次のページの Push an existing Git repository の手順で push する。

LANG:console
$ git remote add origin https://myserver.com/gitlab/myname/gitlab-ci-hello.git
$ git push --set-upstream origin --all
Username for 'https://myserver.com': myname
Password for 'https://myname@myserver.com': xxxxxx
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 266 bytes | 266.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://myserver.com/gitlab/myname/gitlab-ci-hello.git
 * [new branch]      master -> master
branch 'master' set up to track 'origin/master'.
$ git push --set-upstream origin --tags
Username for 'https://myserver.com': myname
Password for 'https://myname@myserver.com': xxxxxx
Everything up-to-date

push 時には gitlab サーバーのユーザー名とパスワードを聞かれるので入力する。

GitLab の画面で確認すると、

jobs-success.png

こちらのようにコミット番号の隣に緑のチェックマークがついて、 ジョブが成功したことを確認できる。

このチェックをクリックして中を見ると、

LANG:console
Running with gitlab-runner 17.3.1 (66269445)
  on myrunnner1 t2X9wkLjw, system ID: r_FoHQflE05Zou
Preparing the "shell" executor 00:00
Using Shell (bash) executor...
Preparing environment 00:00
Running on 8163caea6de5...
Getting source from Git repository 00:00
Fetching changes with git depth set to 20...
Initialized empty Git repository in /home/gitlab-runner/builds/t2X9wkLjw/0/gitlab/myname/gitlab-ci-hello/.git/
Created fresh repository.
Checking out cc71a4d9 as detached HEAD (ref is master)...
Skipping Git submodules setup
Executing "step_script" stage of the job script 00:00
$ echo hello `pwd`
hello /home/gitlab-runner/builds/t2X9wkLjw/0/gitlab/myname/gitlab-ci-hello
$ ls -la
total 16
drwxrwxr-x 3 gitlab-runner gitlab-runner 4096 Sep  7 13:03 .
drwxrwxr-x 4 gitlab-runner gitlab-runner 4096 Sep  7 13:03 ..
drwxrwxr-x 6 gitlab-runner gitlab-runner 4096 Sep  7 13:03 .git
-rw-rw-r-- 1 gitlab-runner gitlab-runner   53 Sep  7 13:03 .gitlab-ci.yml
Cleaning up project directory and file based variables 00:00
Job succeeded

のようにして、

LANG:console
$ echo hello `pwd`

LANG:console
$ ls -la

が正しく実行されていることを確認できる。

ワーキングフォルダが /home/gitlab-runner の下にあることからもわかるとおり、 このシェルは gitlab-runner というユーザー権限で実行される。

そのままでは sudo は使えないので、システムに変更を加えるようなコマンドは実行できない。

Runner の Dockerfile はこちらにある:
https://hub.docker.com/r/gitlab/gitlab-runner/dockerfile

中身はものすごく古い ubuntu を元にしている?

なんでこんなに古いんだろう???

CI スクリプトの書き方

概要:
https://gitlab-docs.creationline.com/ee/ci/yaml/gitlab_ci_yaml.html

リファレンス:
https://gitlab-docs.creationline.com/ee/ci/yaml/index.html

用語の整理:

  • パイプラインは複数のステージから構成される
  • 個々のステージは並列実行可能な複数のジョブから構成される
  • 個々のステージは前のステージのジョブがすべて成功してから次へ進む

node 環境を準備&ビルド&テスト

例えば、以下の .gitlab_ci.yml と package.json をコミットしてみる。

.gitlab_ci.yml

variables:
 NODE_VERSION: 22
 NVM_VERSION: v0.40.0

stages:
  - build
  - test
  - package

cache:
  paths:
    - dist/*

# common setting for each job
default:
  before_script:
    - ls -d "$HOME/.nvm" || (curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh | bash)
    - export NVM_DIR="$HOME/.nvm"
    - \[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
    - nvm --version
    - nvm use ${NODE_VERSION} || nvm install ${NODE_VERSION}

# each job is associated to a stage
build:
  stage: build
  script:
    - echo $CI_PIPELINE_SOURCE
    - echo $CI_COMMIT_REF_NAME
    - echo $CI_COMMIT_TAG
    - echo $CI_COMMIT_BRANCH
    - npm run lint
    - npm run build
  environment: production

test:
  stage: test
  script:
    - npm run test:unit --run
 
package:
  stage: package
  only:
    - tags
  except:
    - branches
  script:
    - ls dist
  artifacts:
    name: mypackage-${CI_COMMIT_TAG}.tar.gz
    paths:
      - dist/*

package.json

LANG:console
{
        "name": "mypackage",
        "version": "0.0.1",
        "scripts": {
                "lint": "echo 'lint - success!'",
                "build": "mkdir -p dist && (echo 'Hello!' > dist/hello.txt) && echo 'build - success!'",
                "test:unit": "echo 'test:unit - success!'"
        }
}

すると build と test に成功して、

build の結果:

LANG:console
Running with gitlab-runner 17.3.1 (66269445)
  on bee01 t2X9wkLjw, system ID: r_FoHQflE05Zou
Preparing the "shell" executor 00:00
Using Shell (bash) executor...
Preparing environment 00:00
Running on 8163caea6de5...
Getting source from Git repository 00:00
Fetching changes with git depth set to 20...
Reinitialized existing Git repository in /home/gitlab-runner/builds/t2X9wkLjw/0/gitlab/osamu/gitlab-ci-hello/.git/
Checking out 504e92af as detached HEAD (ref is master)...
Skipping Git submodules setup
Executing "step_script" stage of the job script 00:07
$ ls -d "$HOME/.nvm" || (curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh | bash)
ls: cannot access '/home/gitlab-runner/.nvm': No such file or directory
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 16555  100 16555    0     0  59336      0 --:--:-- --:--:-- --:--:-- 59336
=> Downloading nvm from git to '/home/gitlab-runner/.nvm'
=> Cloning into '/home/gitlab-runner/.nvm'...
* (HEAD detached at FETCH_HEAD)
  master
=> Compressing and cleaning up git repository
=> Profile not found. Tried ~/.bashrc, ~/.bash_profile, ~/.zprofile, ~/.zshrc, and ~/.profile.
=> Create one of them and run this script again
   OR
=> Append the following lines to the correct file yourself:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
=> Installing Node.js version 22
Downloading and installing node v22.8.0...
Downloading https://nodejs.org/dist/v22.8.0/node-v22.8.0-linux-x64.tar.gz...
######################################################################## 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v22.8.0 (npm v10.8.2)
Creating default alias: default -> 22 (-> v22.8.0 *)
=> Node.js version 22 has been successfully installed
=> Close and reopen your terminal to start using nvm or run the following to use it now:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
$ export NVM_DIR="$HOME/.nvm"
$ \[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
$ nvm --version
0.40.0
$ nvm use ${NODE_VERSION} || nvm install ${NODE_VERSION}
Now using node v22.8.0 (npm v10.8.2)
$ npm run lint
> mypackage@0.0.1 lint
> echo 'lint - success!'
lint - success!
$ npm run build
> mypackage@0.0.1 build
> mkdir -p dist && (echo 'Hello!' > dist/hello.txt) && echo 'build - success!'
build - success!
Cleaning up project directory and file based variables 00:00
Job succeeded

test の結果:

LANG:console
Running with gitlab-runner 17.3.1 (66269445)
  on bee01 t2X9wkLjw, system ID: r_FoHQflE05Zou
Preparing the "shell" executor 00:00
Using Shell (bash) executor...
Preparing environment 00:00
Running on 8163caea6de5...
Getting source from Git repository 00:01
Fetching changes with git depth set to 20...
Reinitialized existing Git repository in /home/gitlab-runner/builds/t2X9wkLjw/0/gitlab/osamu/gitlab-ci-hello/.git/
Checking out ae25c69e as detached HEAD (ref is master)...
Removing dist/
Skipping Git submodules setup
Executing "step_script" stage of the job script 00:00
$ ls -d "$HOME/.nvm" || (curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh | bash)
/home/gitlab-runner/.nvm
$ export NVM_DIR="$HOME/.nvm"
$ \[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
$ nvm --version
0.40.0
$ nvm use ${NODE_VERSION} || nvm install ${NODE_VERSION}
Now using node v22.8.0 (npm v10.8.2)
$ npm run test:unit --run
> mypackage@0.0.1 test:unit
> echo 'test:unit - success!'
test:unit - success!
Cleaning up project directory and file based variables 00:00
Job succeeded

となる。

ちゃんと初回に node がインストールされて、2回目はそれが再利用されている。

ちゃんと test をするときは report を作成するといいらしい
https://docs.gitlab.com/17.3/ee/ci/testing/unit_test_reports.html

パッケージング

まず、上で

cache:
  paths:
    - dist/*

をしておかないと build で作成したファイルが package に引き継がれないので注意が必要だ。

そして package ステージには

  only:
    - tags
  except:
    - branches

がついているので、tag を付けたときで、なおかつそのタグがデフォルトブランチ(ここでは master)についてないとスクリプトは実行されない。

あたりを参照。

試してみよう

GitLab 画面のプロジェクトページの左上 "+" のドロップダウンから New Tag を選んで、

https://myserver.com/gitlab/myname/mypackage/-/tags/new

から 0.0.1 というタグを作成する。

・・・ん? 新しいパイプラインが生成されないような???

https://forum.gitlab.com/t/pipeline-not-executed-after-commit/87173/6

によると GitLab と Runner のバージョンを合わせた方がいいと書かれてる?

GitLab のバージョンは、

$ docker compose exec web gitlab-rake gitlab:env:info

とするか、GitLab 画面左下の Help ボタンを押すことで確認できる。

ここでは 17.3.1 だった。

Runner のバージョンは GitLab の admin メニューから CI/CD - Runners で

https://myserver.com/gitlab/admin/runners

ここから個々の Runner をクリックして show detail すると、、、17.3.1 なので問題なし。

ああ、違った。

ちゃんと動いているけれど、トップ画面の緑色のチェックマークはあくまでコミットに対する CI/CD の結果で、タグに対する CI/CD の結果は Tags のページでタグの横に現れるのか。

個別のタグのページに CI/CD の結果を表示してくれないので混乱した。

https://myserver.com/gitlab/myname/gitlab-ci-hello/-/tags/0.0.1

パイプラインがすべて正しく実行されると、

https://myserver.com/gitlab/myname/gitlab-ci-hello/-/tags

のタグの横に緑のチェックが出て、その右のダウンロードメニューに artifact - package が現れる。

選択すると mypackage-0.0.1.tar.gz を落とせた。

メニュー自体の表示を mypackage-0.0.1.tar.gz にすることはできないのかな???

TODO: リリース時にこのアーティファクトを参照する方法も調べる

GitLab に Runner を登録する(docker モード)

node は sudo の必要なくインストールできるので shell モードで簡単に動かせたけれど、 例えば playwright を動かそうとすると apt などでネイティブなパッケージをいろいろ入れなければならず、 困ってしまう。

そういう時は

https://qiita.com/yusayoshi/items/6b48bca5b5c0a17cd40e#gitlab-cicd

のように runner を docker モードで動かしてやればいいいいみたい。

これは、docker の中で走っている Runner が、さらに docker を起動して ci スクリプトをその docker の中で実行させるというもの。いわゆる dind (docker-in-docker) という技術らしい。Runner の docker コンテナを作る際に /var/run/docker.sock を受け渡しているおかげでそういうことができるのだとか。

Runner を docker モードで動かすのは簡単で、

上記手順で Runner を GitLab へ追加する際に
Executor を shell ではなく docker とする。

すると、デフォルトで使う docker image を聞かれる。

ここでは主に node 上での開発を考えているので playwright が使えるようリンク先にあった

mcr.microsoft.com/playwright:focal

を指定した。

ci スクリプトから image を指定されないときにはこれが使われることになる。 スクリプトで image 指定すれば別の image を使うこともできる。

例えば rails を使いたいならそういうイメージを持ってくればいい。

TODO: GitLab には Docker Image のサーバーにもなれるらしいので、 自分で作ったものを登録しておいて使うなんてこともできるのかもしれない???

extra_hosts を設定する

上で runner に extra_hosts を追加している場合には、

runner/config.toml にも、

[[runners]]
  [runners.docker]
    extra_hosts = ["myserver.com:192.168.0.2"]

の設定をしないと、runner docker 内部で作成される docker コンテナに hosts の設定が引き継がれないことに注意が必要。

試してみる

これで始めから npm や playwright の使える環境で ci スクリプトを走らせることができるようになったはず。

上のスクリプトの before_script をバージョン確認だけにしてみた。

default:
  before_script:
    - node --version
    - npm --version

すると、

LANG:console
$ node --version
v20.16.0
$ npm --version
10.8.1

と表示され、残りのスクリプトも正しく実行された。

GitLab の npm パッケージサーバーへパッケージを Deploy する

GitLab には npm パッケージサーバーの機能があるので、 そこへパッケージを Deploy してみよう。

GitHub のプロジェクトをクローンして GitLab へ登録する

まず、こちらで作った typescript-vitest-skeleton という GitHub プロジェクトをクローンして、 GitLab へ登録しなおす

https://dora.bk.tsukuba.ac.jp/~takeuchi/?typescript+%2B+vitest+%E3%81%A7+npm+%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%AE%E9%96%8B%E7%99%BA+2024

GitLab 上で typescript-vitest-skeleton という空のプロジェクトを作成しておいて、

LANG:console
$ git clone https://github.com/osamutake/typescript-vitest-skeleton.git
Cloning into 'typescript-vitest-skeleton'...
$ cd typescript-vitest-skeleton/
$ npm i
$ git remote add origin2 https://myserver.com/gitlab/myname/typescript-vitest-skeleton.git
$ git push origin2 --all

左のメニューの Deploy - Package Registray からこの GitLab プロジェクトのパッケージレジストリを確認できる。

https://myserver.com/gitlab/myname/typescript-vitest-skeleton/-/packages

当然今は空だ。

ここに上記 npm パッケージを登録したい。

パッケージ名のスコープ

GitLab プロジェクトのパッケージレジストリへパッケージを登録するには、

https://myserver.com/gitlab/api/v4/projects/(project id)/packages/npm/@(scope_name)/(package_name)

のようなアドレスへパッケージを PUT することになる。

ここで、

  • project id : この GitLab サーバーのプロジェクトにつけられた通し番号
  • scope_name : npm ではパッケージ名の前に @ユーザー名 を付けることでスコープ付きにして、他のパッケージと名前が被らないようにしますが、それと似たような識別子
    • GitLab で使うときは GitLab サーバーを識別するために使うことになるみたい?
  • package_name : 例えば上の typescript-vitest-skeleton

まだよくわかっていないのだけれど、どうやら GitLab の提供する npm パッケージサーバーからパッケージをダウンロードさせたい場合「"@osamu_takeuchi/*" のように @osamu_takeuchi というスコープがついているパッケージは npm ではなく特定の GitLab サーバーを探しに行け」という設定をすることになるみたいなので、パッケージ名を例えば @myserver/typescript-vitest-skeleton というような、分かりやすいスコープ付きにしておくのが良さそう?

→ これ、まるっきり間違いではないけれど、正解とは言えない理解だったことが下で明らかに。。。

LANG:console
$ git remote remove origin
$ git remote remove origin2
$ git remote add origin https://myserver.com/gitlab/myname/typescript-vitest-skeleton.git
$ sed -i.bak s/@osamu_takeuchi/@myserver/ package.json 
$ git add package.json
$ git commit -m "Scope of package is changed"
$ git push origin

デプロイ用のトークンを発行して .npmrc を設定する

パッケージのアップロードには個々のプロジェクトからのアクセス許可が必要なので、

プロジェクトの Settings - Repository - Deploy Tokens - Add token からトークンを発行する。

https://myserver.com/gitlab/myname/typescript-vitest-skeleton/-/settings/repository

add-deploy-token.png

GitLab はいろんなところでいろんな用途のトークンを発行できるので、 違うところで違う用途のトークンを発行して設定して、動かないなあ、となることが多い(汗

  • Name を npm push とする
  • write_package_registry にチェックを入れる
  • [Create deploy token] を押す
  • 次の画面で gldt-XXXXXXXXXXXXXXXX のようなトークンをコピーする

プロジェクトのフォルダーにこのトークンを指定した .npmrc を作成する。

LANG:console
$ echo ".npmrc" >> .gitignore
$ cat <<"EOS" >.npmrc
//myserver.com/gitlab/api/v4/projects/9/packages/npm/:_authToken=gldt-XXXXXXXXXXXXXXXXX
@myserver:registry=https://myserver.com/gitlab/api/v4/projects/9/packages/npm/
EOS

このファイルを GitLab へアップロードしてしまうと誰でもデプロイできるようになってしまうので、 必ず .gitignore に追加しておく必要がある。

後日追記 > どうやらそうではなく、

LANG:console
$ cat <<"EOS" >.npmrc
//myserver.com/gitlab/api/v4/projects/9/packages/npm/:_authToken=${NPM_TOKEN}
@myserver:registry=https://myserver.com/gitlab/api/v4/projects/9/packages/npm/
EOS

としておいて、npm publish する際に

LANG:console
$ NPM_TOKEN=gldt-XXXXXXXXXXXXXXXXX npm publish

あるいは .env に NPM_TOKEN=gldt-XXXXXXXXXXXXXXXXX を書いておいて、

LANG:console
$ dotenv npm publish

のようにした方が使いやすいみたいだ。これなら .npmrc を git でコミットして問題ない。

その際、Deploy Token ではなく下で作成する Personal Access Token でもデプロイできるようだったので、それで足りるならわざわざ Deploy Token を作る必要はないのかもしれない。

手動でデプロイする

後は普通に(あるいは必要に応じて dotenv 付きで) npm publish すればいい。

LANG:console
$ npm publish
npm notice Publishing to https://myserver.com/gitlab/api/v4/projects/9/packages/npm/ with tag latest and default access
+ @myserver/mypackage@0.0.1

これでパッケージがデプロイされ、Package Registry に

@myserver/mypackage (latest)
0.0.1 npm

のような項目が現れる。

CI スクリプトからの自動デプロイとの兼ね合いや、上記以外の設定を .npmrc に書きたい場合があることなどを考慮すると、

  • .npmrc に直接 Token を書き込むのではなく実行時に NPM_TOKEN を読ませる方が良い
  • .env に保存しておいて dotenv 付きで npm publish すれば手軽

なので、今後はそのようにする。

Package Registry のパッケージを npm install する

https://myserver.com/gitlab/myname/typescript-vitest-skeleton/-/packages

の一覧から先にデプロイした 0.0.1 をクリックすると、

LANG:console
$ echo @myserver:registry=https://myserver.com/gitlab/api/v4/packages/npm/ >> .npmrc
$ npm i @myserver/mypackage

のようにすればインストールできると書かれていた。

ただし認証が必要になると。

ここでは読み取りのみの権限があればいいので、

トップページのサイドバーの右上アイコンから Edit Profile、サイドバーの User settings - Access Tokens - Add new token を選択。

https://myserver.com/gitlab/-/user_settings/personal_access_tokens

  • Token name を例えば npm install とかにして
  • read_api だけチェックして
  • Expiration date を適当に延ばして [Create personal access token]
  • 次のページでトークンをコピーする (glpat-XXXXXXXX みたいなの)

そして、

https://docs.gitlab.com/ee/user/packages/npm_registry/#authenticate-to-the-package-registry

を見ながら、

LANG:console
$ npm config set @myserver:registry https://myserver.com/api/v4/packages/npm/
$ npm config set -- //myserver.com/api/v4/packages/npm/:_authToken=glpat-XXXXXXXX
$ cat ~/.npmrc
@myserver:registry=https://myserver.com/api/v4/packages/npm/
//myserver.com/api/v4/packages/npm/:_authToken=glpat-XXXXXXXX

これらが ~/.npmrc に書き込まれるのでその後は自由に npm i が使えるはず。

試しに、

LANG:console
$ mkdir ~/test_npm_install
$ cd ~/test_npm_install
$ npm init
{
  "name": "test_npm_install",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": ""
}
$ npm install @myserver/mypackage
npm error code E404
npm error 404 Not Found - GET https://myserver.com/api/v4/packages/npm/@myserver%2fmypackage
npm error 404
npm error 404  '@myserver/mypackage@*' is not in this registry.
npm error 404
npm error 404 Note that you can also install from a
npm error 404 tarball, folder, http url, or git url.

ん?そうでもないぞ。

そんなパッケージは見つからないと言われる・・・って URL を指定し間違っていた。

今は gitlab/ というサブフォルダで gitlab を動かしているので GitLab が教えてくれていた通り、

LANG:console
$ npm config set @myserver:registry https://myserver.com/gitlab/api/v4/packages/npm/
$ npm config set -- //myserver.com/gitlab:_authToken=glpat-XXXXXXXX

が正しい。

でもそうしたら今度は

LANG:console
$ npm install @myserver/mypackage
npm error code E404
npm error 404 Not Found - GET https://registry.npmjs.org/@myserver%2fmypackage - Not found
npm error 404
npm error 404  '@myserver/mypackage@*' is not in this registry.
npm error 404
npm error 404 Note that you can also install from a
npm error 404 tarball, folder, http url, or git url.

となってしまった。

URL のパスが api から始まっていないと認識してくれない???→ そういうことじゃないらしい。

手で試してみると、

https://myserver.com/gitlab/api/v4/packages/npm/@myserver%2fmypackage

へアクセスすると

https://registry.npmjs.org/@myserver%2fmypackage

へのリダイレクトを返してくるようだ。

適当に、絶対存在しないパッケージ名を与えても同じ動作をするので、恐らく自分が持っていないパッケージに対するフォールバック動作がこれみたい。

つまり、プロジェクトへ Deploy したパッケージがここに見つからない??

なんでだろう???

いろいろ試すと、

つまり https://myserver.com/gitlab/api/v4/project/9/packages/npm/@myname/mypackage へアップロードしたのだから同じアドレスへ取りに来いと。

至極当然なのだけれど、それぞれ異なるプロジェクトで作って異なるプロジェクトにアップロードしたパッケージについて、それぞれどこにダウンロードしに行くかプロジェクト番号を覚えておかなければならないのは面倒くさすぎる。

https://docs.gitlab.com/ee/user/packages/npm_registry/#naming-convention

If you plan to install a package from an instance, then you must name your package with a scope. Scoped packages begin with a @ have the format of @owner/package-name. You can set up the scope for your package in the .npmrc file and by using the publishConfig option in the package.json.

- The value used for the @scope is the root of the project that is hosting the packages and not the root of the project with the source code of the package itself. The scope should be lowercase.

この部分、何を言っているのかよくわからないのだけれど、その後の例を見るとスコープ名は GitLab 上のプロジェクトのパスの初めの部分(グループ名?)と一致していなければならない、ということかもしれない???

グループを作成してプロジェクトをグループへ移す

  • Admin メニューから Private グループ myserver を作成
    • 本当は mygroup としたいところだけれど上で @myserver/mypackage という名前にしてしまっているのでそれと合わせた
  • typescript-vitest-skeleton プロジェクトの Settigs - General - Advanced - Transfer Project - Select a new namespace - myserver として [Transfer project]

これで https://myserver.com/gitlab/api/v4/packages/npm/@myserver/mypackage へアクセスしたところ、正しくパッケージにアクセスできた。

しかし

LANG:console
$ npm i @myserver/mypackage
npm error 404 Not Found - GET https://registry.npmjs.org/@myserver%2fmypackage - Not found
npm error 404
npm error 404  '@myserver/mypackage@*' is not in this registry.
npm error 404
npm error 404 Note that you can also install from a
npm error 404 tarball, folder, http url, or git url.

となるのは変わらない。

問題は、

https://registry.npmjs.org/@myserver/mypackage

ではなく

https://registry.npmjs.org/@myserver%2fmypackage

へリダイレクトされていること。

なぜか / がエンコードされてしまっていて、そのせいで見つからない。

.npmrc の参照先をフロントの myserver.com ではなく、 GitLab docker の http://localhost:8980/gitlab/... に変更すると繋がるので、 この問題は間に入っている apache2 のプロキシにあると思われるのだけれど、 書き込み時に問題にならないのに読み出し時に問題になるのはなぜ???

フロントの apache2 のログ:

GET /gitlab/api/v4/packages/npm/@myserver%2fmypackage

GitLab docker 内の nginx のログ:

GET /gitlab/api/v4/packages/npm/@myserver%252fmypackage

あちゃあ、これは。。。

https://yomon.hatenablog.com/entry/2017/10/20/222331

この記事に答えがありました。

apache2 はそのまま受け渡そうとしているのに mod_proxy が変換してくれやがってくださっているので(ママ)、ProxyPass の最後に nocanon を付けなさいと。

    ProxyPass /gitlab http://localhost:8980/gitlab

を、

    ProxyPass /gitlab http://localhost:8980/gitlab nocanon

ですね。

これで問題なくインストールできるようになりました。

うーむ・・・遠いなあ。

まとめると、サーバーレベルのリポジトリで npm パッケージを配布する場合には、

  • GitLab に最低1つのグループを作る
  • npm を配布するプロジェクトは必ずいずれかのグループに入れる
  • 配布する npm パッケージ名につけるスコープ (@scope/name のようなパッケージ名の / より前の部分) はGitLab プロジェクトの属するグループ名(サブグループに属する場合にはトップのグループ名)に合わせる

が必要、ということになる。

でもこれだと、せっかくサーバーレベルでリポジトリを公開したとしても、 結局 .npmrc にはスコープごとの設定が必要で、なんだか大きなメリットを感じにくい???

スコープはグループのリポジトリに結びつけることもできる。

# グループ番号が 12 の場合の .npmrc の設定
@myserver:registry=https://myserver.com/gitlab/api/v4/groups/12/-/packages/npm/
@mygroup:registry=https://myserver.com/gitlab/api/v4/groups/12/-/packages/npm/
@myscope1:registry=https://myserver.com/gitlab/api/v4/groups/12/-/packages/npm/
@myscope2:registry=https://myserver.com/gitlab/api/v4/groups/12/-/packages/npm/

この場合には必ずしもスコープ名とグループ名を一致させる必要はないそうなので大幅に自由度が上がる。 いろんなスコープを1つのグループに結びつけておけばやりたい放題だ(?)

グループ内にその名前のパッケージがあればそれを返し、 もしなければ npmjs.org に飛ばしてくれることを考えると、

registry=https://myserver.com/gitlab/api/v4/groups/12/-/packages/npm/

のようにデフォルトのリポジトリをここにしてしまうというのも手なのかも。

その形であれば、公開されてる npm パッケージにパッチを当てたものをここに置いて差し替えて使ったりもできるわけだし。

そういう使い方なら、見つからないときに npmjs.org に飛ばしてくれるのは良しあしという気もする。

registry= の設定を上から探して、見つかったものを使う、みたいな動作を npm がしてくれる方がさらに便利だ。

npm 自体がそういう設計にはなっていないということなのかな。(未検証)

CI スクリプトによる自動デプロイ

あら?

https://gitlab-docs.creationline.com/ee/user/packages/npm_registry/#publishing-a-package-via-a-cicd-pipeline-1

を見るとすごく簡単なように書かれてる?

deploy:
  stage: deploy
  script:
    - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}">.npmrc
    - npm publish

手動でやるときに頑張って調べたり作ったりしたプロジェクトIDやらデプロイ用の Token やらは初めから環境変数として渡されているから、それを使えばいいと。

ここではすでに上記の NPM_TOKEN を含む .npmrc をコミットしてあるので、.gitlab-ci.yml にて

stages:
  - deploy

deploy-job:
  stage: deploy
  environment: production
  only:
    - tags
  except:
    - branches
  script:
    - npm i
    - NPM_TOKEN=$CI_JOB_TOKEN npm publish

とすればタグ追加時に CI スクリプトから npm パッケージをデプロイできた。

コメント・質問





Counter: 250 (from 2010/06/03), today: 4, yesterday: 4