bitbank techblog

npm パッケージでも CI/CD がしたい!

はじめに

こんにちは。 suzukirito です。フロントエンドエンジニアですが、最近は Node.js アプリケーションの開発をはじめとして、 docker や CI 環境の整備など幅広く触れていて楽しんでいます。

ビットバンクでは Node.js を全面採用していますが、自作ライブラリの管理方法を現在鋭意刷新中ですので、その一部である「npm パッケージの CI/CD 化」について紹介したいと思います!

CI/CD とは

CI は Continuous Integration の略であり、一般的には継続的にプロダクトが正しい状態であることを検証するために自動テストをリポジトリに含めることを指します。

CD は Continuous Delivery の略であり、継続的にプロダクトを Delivery (アプリケーションにおいてはリリース) することです。

具体的にビットバンクでは Gitlab でソースコードを管理し、 Gitlab のビルトイン機能である GitlabCI を用いて自動テストを実行し、 production へのマージを以ってデプロイされる方式を採用中です。

npm モジュールの CI/CD

上記の CI/CD の手法は主にアプリケーションで用いられがちですが、ビットバンクでは npm モジュールにも CI/CD を取り入れました。

大きな理由としては主に 2 つあります。

  • リリース承認 = マージ = リリースを一元化したい
  • 手元での publish によるリスクを排除したい

前者はそのまま、承認をもらってからリリースという作業を行うため、手間と時間がかかるという問題があります。

後者の publish を行う人やマシンへの依存することによるリスクとして、ざっくり以下のような問題があると感じます。

  • publish を行う人/タイミングにより、 node インタプリタやライブラリのバージョンが異なる恐れがある
  • publish を行った際のコマンドや、 node のバージョンが記録されない
  • リポジトリと異なるソースコードを publish することが原理的に可能である
  • (本人の意図するところであるなしにかかわらず)マルウェアが混入する恐れがある

このことから、ビットバンクではより堅牢でトレーサビリティのある方法として CD を採用しました。

GitlabCI のうれしい機能の紹介

Gitlab を採用していることから GitlabCI を採用した、ということもありますが、GitlabCI はかゆいところに手が届く機能を多く備えています。

なお GitlabCI は GitHub からでも使用できます! CI と docker で消耗している方は試してみてはいかがでしょうか。

docker in docker が手軽に行える

ライブラリによってはテストに外部のイメージが必要になることがあると思います。
MySQL や Redis 等の public image であれば、 CircleCI でも容易に使うことができます。
しかし自社のアプリケーションを起動してテストする場合などは、リポジトリが同一であれ別であれ、起動への依存を極小にするために docker-compose を使うのが良いと考えます。
また、CircleCI や GitlabCI で標準提供されている Service の場合

  • port のフォワードが変えられない(ので複数台建てられない)
  • 名前解決がローカルともプロダクションとも異なる

などの実装への副作用を含む側面もあります。
そのため、ビットバンクでは MySQL や Redis も docker-compose で詳細に設定を管理しています。

環境変数の権限管理が行える

npm publish が可能な token を全員が見れる箇所に配置するのは内部統制の観点から良くないです。
GitlabCI では protected environment という機能があり、 protected branch の CI でのみ 参照される環境変数が設定できます。
protected branch は権限保持者しかマージできず、かつマージされたときにしか CI が走らないので、 echo する CI を入れて覗くことはできません。安全ですね。

実際にビットバンクでやっている CI/CD

テストの実行

ユニットテストのほか、 docker-compose を用いてサーバを起動した状態でのクライアントのテストなどを実行します。
多くの場合、ビルドチェック、 Lint や Format 漏れ検出もここのフェーズに含むのではないかと思います。

npm audit の実行

npm6 以降では依存パッケージの脆弱性検査を行える npm audit というコマンドが追加されました。
npm audit: identify and fix insecure dependencies
Hosting の Gitlab であるため、 greenkeeper に対応していないので、継続的に脆弱性検査を行うために以下の手法を検討しています。

  • CI を定期的に実行し、エラーがあった際にアラートを飛ばす
  • hothouse 等のオープンソースに gitlab 対応をコントリビュートして使用する

npm パッケージであるため、audit した時のライブラリのバージョンとアプリケーションで使用されるライブラリのバージョンが異なってしまうと脆弱性検知漏れの可能性があります。
そのためパッケージ側では lock ファイルではなく package.json での公開パッケージ指定は ^ を使用せず、バージョン固定しています。

npm prepare の実行

node8(npm5)以降では npm prepublish を廃止して prepare を使用してビルドフローを実行しています。
とはいえ npm prepublish の現状と今後どう変わっていくか によると prepublish / prepublishOnly / prepare は今後変更が入ることがわかっているため、適切なタイミングで prepublish に変更しようと考えています。

npm can publish の実行

azu さんの npm publish できるかを判定するコマンドラインツール: can-npm-publishを使用し、バージョンがインクリメントされているかを確認しています。
これは master branch のみで実行し、 master の CI が通った状態でのみ production にマージできるようにしています。

npm publish の実行

これは protected である production branch のみで実行します。
production branch へのマージは権限保持者のみができるため、内部統制も兼ねています。

サンプルコード

では実際にどういうコードになるのか、というのを紹介します。

.gitlab-ci.yml

ベースイメージは docker:dind (alpine) をベースに指定の Node のバージョンを入れたものを使っています。

image: your.cool.registory/node-6.15.1-alpine-dind/node:6.15.1-alpine-dind
stages:
  - test
  - publish
npm-test:
  stage: test
  script:
    - dockerd-entrypoint.sh &
    - touch ~/.npmrc
    - echo "//registry.npmjs.org/:_authtoken=${NPMJS_READ_TOKEN}" >> ~/.npmrc
    - printf "cat <<++eos\n`cat test/config/json/aws.json.org`\n++eos\n" | sh > test/config/aws.json
    - apk add curl mysql-client redis
    - npm install
    - npm run ci:test
npm-lint:
  stage: test
  script:
    - touch ~/.npmrc
    - echo "//registry.npmjs.org/:_authtoken=${NPMJS_READ_TOKEN}" >> ~/.npmrc
    - printf "cat <<++eos\n`cat test/config/json/aws.json.org`\n++eos\n" | sh > test/config/aws.json
    - npm install
    - npm run ci:lint
npm-prepare:
  stage: test
  script:
    - touch ~/.npmrc
    - echo "//registry.npmjs.org/:_authtoken=${NPMJS_READ_TOKEN}" >> ~/.npmrc
    - printf "cat <<++eos\n`cat test/config/json/aws.json.org`\n++eos\n" | sh > test/config/aws.json
    - npm install
    - npm run prepare
npm-audit:
  stage: test
  script:
    - touch ~/.npmrc
    - echo "//registry.npmjs.org/:_authtoken=${NPMJS_READ_TOKEN}" >> ~/.npmrc
    - npm install -g npm@6
    - npm install --package-lock-only
    - npm audit
can-publish:
  stage: test
  script:
    - touch ~/.npmrc
    - echo "//registry.npmjs.org/:_authtoken=${NPMJS_READ_TOKEN}" >> ~/.npmrc
    - npm install -g npm@6
    - npm install -g can-npm-publish
    - can-npm-publish
  only:
    - master
publish-production:
  stage: publish
  script:
    - touch ~/.npmrc
    # この token は production branch の CI でしか参照されない
    - echo "//registry.npmjs.org/:_authtoken=${NPMJS_PUBLISH_TOKEN}" >> ~/.npmrc
    - npm install
    - npm publish
  only:
    - production

終わりに

ここまでお読みいただきありがとうございました。私が 3 年ほど愛用している GitlabCI の魅力がみなさんに届けばうれしく思います。

このように、ビットバンクではアプリケーション以下のレイヤでも新しい技術を積極的に導入することで、効率的かつ安全なシステム開発を推進しています。

ビットバンクでは Node.js を好きな人のほかにも、このように開発環境を加速したいエンジニアも募集しています。
ご興味を持たれましたら、まずはぜひお話を聞きに来てください!

ビットバンク株式会社 | 採用サイト

Author image
About suzukirito
二刀流キーボードで 16 回連続打鍵ができます。
expand_less