Technote

by sizuhiko

Serverless Frameworkでプライベートパッケージを利用するときの注意点

この記事はDependabotをGHEのプロジェクトに適用するの続編です。 少し前提となる条件があるので、読んでいただけるとスムーズに進みます。

ある日事件は起こった

毎朝出社すると、dependabotから出てくるPRをチェックしてマージしています。 プロジェクトでGHEにプライベートなnpmパッケージを作っていて、前日その更新をしたのでアプリ側にPRが来ていました。 修正内容は自分たちが実施したものなので、何も疑うことはなくマージ。テストも通過していました。

developブランチにマージすると、開発環境へデプロイするようになっているので、そのままデプロイされました。 特に問題はないように見えます。

あのー、サーバーからエラーが戻ってくるんですけど…

目の前にはネイティブアプリチームがいます。

「あのー、サーバーのAPIからエラーが戻ってくるんですけど、何かしました?」

あ、さっきデプロイしたけど、プライベートパッケージ更新しただけだぞ…

エラー通知が飛んでくる

ほどなく監視からエラーが。

{
    "errorType": "Runtime.ImportModuleError",
    "errorMessage": "Error: Cannot find module 'psl'",
    "stack": [
        "Runtime.ImportModuleError: Error: Cannot find module 'psl'",
        "    at _loadUserApp (/var/runtime/UserFunction.js:100:13)",
        "    at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)",
        "    at Object.<anonymous> (/var/runtime/index.js:45:30)",
        "    at Module._compile (internal/modules/cjs/loader.js:778:30)",
        "    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)",
        "    at Module.load (internal/modules/cjs/loader.js:653:32)",
        "    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)",
        "    at Function.Module._load (internal/modules/cjs/loader.js:585:3)",
        "    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)",
        "    at startup (internal/bootstrap/node.js:283:19)"
    ]
}

psl というnpmパッケージを使っているのですが、それが見つからないよ、と。

この箇所はユニットテストも通過しているし、なんでだろう?と思い、S3にアップロードされているzipを展開すると確かに node_modules に psl は入っていません…

Serverless Framework におけるパッケージング

sls deploysls package を使ってzipを作成するとき、node_modules には dependencies だけが入り、 devDependencies は取り除かれます。 Lambdaなどはzipファイルのサイズに制限があるので、これはとても便利な機能です。

psl の利用箇所を調べるため、 package-lock.json を探してみます。

    "xxxx-node-fetch": {
      "version": "git+https://xxxxxxxxxxxxxxxx:x-oauth-basic@github.xxxxx.com/xxxx/xxxx-node-fe
tch.git#22141d66ca58e25b9c6f9d5907004802f55afcc4",
      "from": "git+https://xxxxxxxxxxxxxxxxxxx:x-oauth-basic@github.xxxxx.com/xxxx/xxxx-node-fetch
.git#22141d66ca58e25b9c6f9d5907004802f55afcc4",
      "requires": {
        "psl": "^1.7.0",
        "winston": "^3.2.1",
        "winston-cloudwatch": "^2.3.0"
      }
    },

これはプライベートパッケージでGHEのリポジトリを参照しています。 これ以外の利用箇所を探してみました。

    "request": {
      "version": "2.88.0",
      "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
      "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
      "dev": true,
      "requires": {
        "aws-sign2": "~0.7.0",
        "aws4": "^1.8.0",
        "caseless": "~0.12.0",
        "combined-stream": "~1.0.6",
        "extend": "~3.0.2",
        "forever-agent": "~0.6.1",
        "form-data": "~2.3.2",
        "har-validator": "~5.1.0",
        "http-signature": "~1.2.0",
        "is-typedarray": "~1.0.0",
        "isstream": "~0.1.2",
        "json-stringify-safe": "~5.0.1",
        "mime-types": "~2.1.19",
        "oauth-sign": "~0.9.0",
        "performance-now": "^2.1.0",
        "qs": "~6.5.2",
        "safe-buffer": "^5.1.2",
        "tough-cookie": "~2.4.3",
        "tunnel-agent": "^0.6.0",
        "uuid": "^3.3.2"
      },
      "dependencies": {
        "punycode": {
          "version": "1.4.1",
          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
          "dev": true
        },
        "tough-cookie": {
          "version": "2.4.3",
          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
          "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ=
=",
          "dev": true,
          "requires": {
            "psl": "^1.1.24",
            "punycode": "^1.4.1"
          }
        }
      }

そうすると、 request パッケージが参照する tough-cookie が利用しているのがわかります。 "dev": true, になっているので、devDependenciesに指定されているということですね。

ただ、このようなケースはまぁまぁある(依存しているパッケージによっては dependencies だったり devDependencies であったりする)ので、Serverless Framework で良しなにしてくれるはずです。 実際、昨日までは問題なかったのに??

### DependabotのPRを見る

package-lock.json の変更だけで以下のようなdiffになっていました。

-     "version": "git+https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:x-oauth-basic@github.xxxx.com/xxxx/xxxx-node-fetch.git#112cf335236d974538ba02993d5ef7bbbbc3ec4c",
-     "from": "git+https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:x-oauth-basic@github.xxxx.com/xxxx/xxxx-node-fetch.git#develop",
+     "version": "git+https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:x-oauth-basic@github.xxxx.com/xxxx/xxxx-node-fetch.git#a2ad500e4345206e59d06e9112fab8e1ec7517d1",
+     "from": "git+https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:x-oauth-basic@github.xxxx.com/xxxx/xxxx-node-fetch.git#a2ad500e4345206e59d06e9112fab8e1ec7517d1",

package.json には "xxxx-node-fetch": "git+https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:x-oauth-basic@github.xxxx.com/xxxx/xxxx-node-fetch.git#develop", のように指定されていて、developから最新を取得するようになっています。

PRでは version 以外にも from#develop のようなブランチ指定から、コミットハッシュ指定に変わっているのがわかります。

dependencies ではブランチ指定をやめる

lockファイルの from 指定が変わり、package.json の dependencies と釣り合いが取れなくなることで、dependencies に必要のないライブラリと Serverless Framework が認識するようで zip ファイルに含まれなくなっていました。

この状態でも npm inpm ci ではパッケージはインストールできるので UnitTest は通過します。

このような事象から、私たちは一旦 package.json に記述するときにブランチ指定をやめて、コミットハッシュ指定に変更することとしました(つまり from を合わせるということです)。 この修正で無事 zip に psl が入って問題を解決することができました。

さいごに

本来であればOSSにできそうなものは普通にnpmにあげるとかした方が良いのですが、調整が困難なのと非常に短期間でプロジェクトが進んでいたのでOSSにはできませんでした。 また、OSSにできないプライベートなパッケージなどを作る場合は、ローカルレジストリ作った方が良いな、とも思いました(ブランチ指定とかコミットハッシュでなく、ちゃんとバージョン管理できるし)。

プライベートなnpmレジストリを作るには、独自の npm registry を使う が参考になりそうです。

また AWS を使っているのであれば、新しいクイックスタートを使用して JFrog Artifactory を AWS にデプロイ という公式記事があるので、参考になりそうです。

DependabotをGHEのプロジェクトに適用する

昨今のWebアプリケーション開発では、多数のOSSライブラリに依存しています。 私たちが開発するアプリケーションの脆弱性対応はもちろんのこと、これらのライブラリもセキュリティアップデートやバグフィックスなどが行われていきます。

では、ライブラリのバージョンを最新に追従するにはどうしたら良いでしょうか?

  • ライブラリのソースリポジトリをチェックする。GitHubのことが多いので、Watchしておけば通知が届きます。
  • 依存関係をチェックしてくれるBotを入れて、Slackなどに通知する。たとえば npmcheck2slackのようなもの
  • 依存関係をチェックしてPRを出してくれるツールを導入する。

3つ目のPRを出すツールに関しては、対応する言語ごとに様々なものがありますが、今回はGitHubに買収されたことでも有名になったDependabotを導入してみたので解説します。

でもDependabotって、普通にGitHubから使えるでしょ?

そうなんです。ただ今回はGitHubでもGitHub Enterpriseを利用している場合には(まだ?)Dependabotを利用することができないので、CIで実行できるようにしてみたよ!という記事です。

想定されるプロジェクト環境

  • ソースコードリポジトリはGHE
  • CIサーバーにDrone.ioをオンプレ利用

ですが、GHEやDroneでなくても、この記事を理解してもらえれば利用可能になると思います。

つまりリポジトリやらCIやらをオンプレしているプライベートな開発環境で、依存関係更新ツールを使ってみようろいうことです。

Dependabotをオンプレで動かす

DependabotにはDependabot Update Scriptというものがあり、オンプレ環境で動かせるようになっています。

READMEに書いてあるとおり設定すれば良いのですが、私たちのプロジェクトでは以下のようにしました。

  1. dependabot-drone というリポジトリをGHEに作成。これをDroneのdaily cronで実行してPRを日次で作成させる
  2. Dependabot Update Scriptは dependabot-drone に git submodule で追加する
  3. Dependabot Update Scriptの更新もチェックしたいので、 dependabot-drone 自身の更新もチェックする

これをDrone.ioで実行するには、以下のような .drone.yml ファイルを記述します。

clone:
  git:
    image: plugins/git
    recursive: true # 2のとおり submodule を使う場合指定

pipeline:
  # READMEに書いてあるインストール手順を定義
  install:
    image: dependabot/dependabot-core
    pull: true
    commands:
      - cd dependabot-script
      - bundle install -j 3 --path vendor
    when:
      event: cron
  # 3の自身をアップデートする定義
  dependabot-drone:
    image: dependabot/dependabot-core
    environment:
      - GITHUB_ACCESS_TOKEN=xxxxxxxxxxxxxxxxx
      - GITHUB_ENTERPRISE_HOSTNAME=github.xxxxx.com
      - GITHUB_ENTERPRISE_ACCESS_TOKEN=xxxxxxxxxxxxxxxxx
      - PROJECT_PATH=xxxxxx/dependabot-drone
      - PACKAGE_MANAGER=submodules
    commands:
      - cd dependabot-script
      - bundle exec ruby ./generic-update-script.rb
    when:
      event: cron

Dependabotのコア機能は dependabot/dependabot-core というDockerイメージで提供されているので、このイメージを使って dependabot-script を実行するということです。 このときに環境変数を指定します。

  • GITHUBACCESSTOKEN パッケージマネージャ(npmやcomposer, bundlerなど)が参照するリポジトリにGitHub APIを使ってアクセスするため、アクセストークンを設定しないとAPI上限に引っかかってしまいます。このため必ず設定しましょう。
  • GITHUBENTERPRISEHOSTNAME GHEのホスト名を指定します
  • GITHUBENTERPRISEACCESS_TOKEN GHEにアクセスするためのアクセストークンを設定します
  • PROJECT_PATH GHEのプロジェクトパスを指定します
  • PACKAGE_MANAGER ここではsubmodulesを指定しています。他の候補はREADMEに書いてあるとおりソースコードを参照します

アプリケーションリポジトリをチェックしていく

あとは、先ほど作った .drone.yml ファイルにアプリケーションリポジトリを追加してくだけです。

たとえば frontend-app というリポジトリをチェックしたい場合は、このようになります。

# 省略...

pipeline:
  # 省略...
  dependabot-drone:
    # 省略...
  frontend-app:
    image: dependabot/dependabot-core
    environment:
      - GITHUB_ACCESS_TOKEN=xxxxxxxxxxxxxxxxx
      - GITHUB_ENTERPRISE_HOSTNAME=github.xxxxx.com
      - GITHUB_ENTERPRISE_ACCESS_TOKEN=xxxxxxxxxxxxxxxxx
      - PROJECT_PATH=xxxx/frontend-app
      - PACKAGE_MANAGER=npm_and_yarn
    commands:
      - cd dependabot-script
      - bundle exec ruby ./generic-update-script.rb
    when:
      status: [success, failure]
      event: cron

PROJECT_PATHPACKAGE_MANAGER 、パイプラインの名前が違うだけで他の定義はすべて一緒です。 status: [success, failure] を入れているのは、複数リポジトリある場合に、途中で失敗しても継続したいための設定です。

PRがやってくる日々

daily cron に従って Dependabot からPRが送られてきます。

これでWatchしてPRしなくて良いし、とても便利ですよね。 PRのコメントに Changelog や Commit など変更のリンクがあるので、内容をチェックすることもできます。

プライベートでも依存関係の更新をちゃんとやれるよ!ということでした。

2020年の目標と昨年のふりかえり

ふりかえり

昨年は、よりBlog発信、登壇に力を入れていました。 成果としては、Blog記事が41投稿ということで、(2018年は31投稿なので) +10記事増やせました。

これは登壇や中の人としてイベントに参加したことが大きかったのですが、それらをふりかえってみます。

イベント登壇と運営

2019年は、2018年以上にずっとイベントに追われていました。 2018年のふりかえりはこちらを参照ください。

運営/主催/発表/出展などは、その準備だったりミーティングがあったりして、これよりも私のGoogleカレンダーは濃密にイベントスケジュールで埋まっています。 トータルの数としては昨年とほぼ一緒なのですが、参加よりも運営/主催/発表/出展が多かったのも、多忙に感じた原因にはあったかと思います。

なんといっても、全国PHPカンファレンスに発表枠で参加できたことは思い出です。

はじめのおわり

今年は

  • WPSG
  • CHIRIMEN
  • OSS (Fabricateのアプデ頑張る)

あたりが主戦場になると思います。 もちろんカンファレンスなどにも、なるべく参加していきたいとは思っています(今年のようにフル参加は難しいかもしれませんが)。

昨年同様に何をするにも健康第一なので、運動してお酒はほどほどに?みなさまと楽しい1年にできればと思います。 どこかでお会いしましたら、よろしくお願いします!

GDG Devfest 2019 に WPSG として参加してきました

GDG DevFest Tokyo 2019Web Platfoem Study Groupとして、Hands-on Teachersで参加してきました。

Web Codelab ということで、スプレッドシートに最近のCodelabのリストを列挙しました。 これらは、すべて事前にちゃんとできるか確認したので、しばらくは大丈夫だと思います(ブラウザのアップデートなどでエラーになる場合もあります)。

Web Codelab 最新リスト

このリストの選択は、以下の方針となっています。

  • GCPとかクレカ情報とか必要でないもの
  • ほかのページで解説しないもの(Basics of Angularとか見出しページだけで、中は別ページのスライド形式だった…
  • Chromeの翻訳機能で日本語がある程度理解可能なもの

一部うまくいかない箇所もあるので、リストの備考を参考にしてください。

ということで、このDevFestをもって、中の人のコミュニティ活動はすべて終了です。

来年はWPSGで勉強会とかもやりたいなー

明日の開発カンファレンス アスカントークナイトを開催しました

明日の開発カンファレンス アスカントークナイトの中の人として運営参加しました。

今回は「明日の開発カンファレンス年末スペシャル」ということで、以下の趣旨で開催となりました。

開発にまつわる全ての話題について、腹を割って話し合える「明日の開発カンファレンス」 今回は過去のカンファレンスに登壇いただいた皆様により熱く語っていただけるようトークライブ形式のイベント「アスカントークナイト」 を12/6(金)に開催致します。

私は、最初のトークセッションでモデレータを担当しました。

開発現場の生の声 〜様々な障害を乗り越えて〜 とはまた重厚なテーマですが、基本的には3名の登壇者に、登壇してから現在までのアップデートを5分ずつ話してもらい、チャチャをちょっと入れながら進め、最後20分ぐらいをQ&Aセッションにする、という流れでした。

質問ないだろうなぁ?というのはこういうセッションでは あるある なので、ちゃんとお題も2つ用意しておきました。

  • 最近取り入れてみて良いと思ったプラクティス
  • 課題対私たち の構図を生み出すために、良いと思う施策やアイデア

開発現場 ということで、なるべく チーム にフォーカスした話をしたかったのですが、 の話が多くなってしまったのはすり合わせ不足なのか、それが現場のリアルな悩みなのか? ということがありましたが、後で聞くと盛り上がっていて良かったという感想をいただきましたので、 良かったと思います。

当日の雰囲気は アスカンナイトトーク2019 にTogetterまとめがありますので、みていただければと思います。

SNS書けない話もたくさんあったので、次回あるときはみなさまぜひご来場ください。