Node.jsでパッケージが参照しているnode_modulesのパスを取得する

結論

require.resolveを使う

Use the internal require() machinery to look up the location of a module, but rather than loading the module, just return the resolved filename.

const path = require.resolve(<PACKAGE>)

https://nodejs.org/dist/latest-v16.x/docs/api/modules.html#requireresolverequest-options

環境

% node -v
v16.13.2
% npm -v
8.10.0

動作確認

npm init -y
npm init -y -w apps/api-a
npm i cowsay --save-exact
npm i express --save-exact
npm i express@4.0.0 --save-exact -w apps/api-a
require-resolve % tree -L 3 -I node_modules
.
├── apps
│   └── api-a
│       ├── index.js
│       └── package.json
├── package-lock.json
└── package.json

apps/api-a/package.json

{
  "name": "api-a",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "4.0.0"
  }
}

package.json

{
  "name": "require-resolve",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "workspaces": [
    "apps/api-a"
  ],
  "dependencies": {
    "cowsay": "1.5.0",
    "express": "4.18.1"
  }
}

CommonJSの場合

apps/api-a/index.js

const cowsay = require("cowsay");
console.log(require.resolve("cowsay"));

const express = require("express");
console.log(require.resolve("express"));

実行結果

node apps/api-a/index.js
/.../require-resolve/node_modules/cowsay/index.js # さかのぼって参照
/.../require-resolve/apps/api-a/node_modules/express/index.js # 最も近いもの 

挙動の説明

https://nodejs.org/dist/latest-v16.x/docs/api/modules.html#loading-from-node_modules-folders

上のドキュメントにある通り、node_modulesをさかのぼって参照する。
ディレクトリルートにexpress@4.18.1、exapi-aにexpress@4.0.0がインストールされており、
apps/api-a/index.jsは一番近いexpressを見に行く。
どのnode_modulesを利用しているかはrequrie.resolveで取得できる。

ES Modulesの場合

apps/api-a/index.js

console.log(await import.meta.resolve("cowsay"));
console.log(await import.meta.resolve("express"));

apps/api-a/package.json

  "type": "module",

実行結果

% node --experimental-import-meta-resolve apps/api-a/index.js
file:///.../require-resolve/node_modules/cowsay/index.js
file:///.../require-resolve/apps/api-a/node_modules/express/index.js

もしくは

import { createRequire } from "module";
const require = createRequire(import.meta.url);
console.log(require.resolve("cowsay"));
console.log(require.resolve("express"));
% node apps/api-a/index.js
/.../require-resolve/node_modules/cowsay/index.js
/.../require-resolve/apps/api-a/node_modules/express/index.js

参考ドキュメント

https://nodejs.org/dist/latest-v16.x/docs/api/esm.html#importmetaresolvespecifier-parent https://nodejs.org/dist/latest-v16.x/docs/api/esm.html#no-requireresolve

まとめ

どのnode_modulesを参照しているのかパスを取得する方法をまとめた。
モノレポはnode_modulesが複数できるので、node_module内のファイルを参照するときにパスを考慮する必要がある。

ESLintのパフォーマンスを調査する

環境

ESLint v8.13.0

調査方法

ルールごとにかかっている時間の割合を計測する

TIMINGオプションを付けると、実行時間が長い順のトップ10と、lint対象全体に対してのトップ10の割合が表示される

$ TIMING=1 eslint lib
Rule                    | Time (ms) | Relative
:-----------------------|----------:|--------:
no-multi-spaces         |    52.472 |     6.1%
camelcase               |    48.684 |     5.7%
no-irregular-whitespace |    43.847 |     5.1%
valid-jsdoc             |    40.346 |     4.7%
handle-callback-err     |    39.153 |     4.6%
space-infix-ops         |    35.444 |     4.1%
no-undefined            |    25.693 |     3.0%
no-shadow               |    22.759 |     2.7%
no-empty-class          |    21.976 |     2.6%
semi                    |    19.359 |     2.3%

https://eslint.org/docs/developer-guide/working-with-rules#per-rule-performance

debugオプションを使う

eslint src/**/*.ts --debug
eslint --cache src/**/*.ts --debug

下記のような情報が表示される。

  • Loading rule...読み込んでいるルール
  • eslint:linter Linting code for...どのファイルを読み込んでLintしているか
  • Cache hit/No cache found

上記をもとに不要なファイルが読み込まれていないか、キャッシュが効いているかを確認する。

https://eslint.org/docs/user-guide/command-line-interface#--debug

改善方法例

不要なルール、不要なファイルの見直し

キャッシュを使う

--cacheオプションを付けることでキャッシュファイルが生成され、
キャッシュファイルと差分があるもののみlintされる。

timeコマンドで計測した結果は下記

time eslint --cache src/**/*.ts
  • 初回実行時
    8.15s user
  • 二回目
    1.53s user

検証には適当な700行程度のTypeScriptのリポジトリを利用。
eslint-plugin-prettierが入っていたのでもともと遅い。

https://eslint.org/docs/user-guide/command-line-interface#caching

まとめ

ESLintのパフォーマンスを調査する手順をまとめた。
キャッシュオプションに関してはCIでも使えそうなので、今後必要が出たら調査していく。

GitHub Actionsでdocker build時にエラーが出た

概要

GitHub Actionsでdocker build時にエラーが出たのでメモ

エラー

docker build時に下記エラー

error: failed to do request: Post "https://***.dkr.ecr.ap-northeast-1.amazonaws.com/v2/<REPOSITORY>/blobs/uploads/": EOF

原因

ログインするECRのアカウントIDを間違えていた

エラー

docker build時に下記エラー

exporting to image  error: failed to solve: unexpected status: 401 Unauthorized 320 Error: buildx failed with: error: failed to solve: unexpected status: 401 Unauthorized

原因

ECRにログインしてなかった。CIに下記を追加
.github/workflows/build.yml

      - name: Login to Amazon ECR
        uses: aws-actions/amazon-ecr-login@v1.3.3

転職にともないLTした話

概要

年明けに転職に伴って自己紹介のLTをした。
その内容を一部抜粋して記載する。
主にどんなことをやってきたか記載した。

苦労したところとそれに対する取り組み

  • 開発生産性の改善
  • 負荷試験の改善
  • 組織課題の改善

開発生産性改善

辛かったこと

  • 作業の再現性が低い
  • 属人化
  • 作業状況が見えづらい

対策

  • ワークフローのコード化
    このリポジトリはいつ誰がどのようにテスト・ビルド・デプロイするのかコードを見ればわかるようにコード化する

    ビルド、テスト、デプロイ、リリース、負荷試験などは繰り返される事が確定しているので、初期段階で自動化する

  • CIテンプレート作成

    • 各マイクロサービスはGitHub Template Repository
      • reusable workflow
    • GitLabはここらへん充実していた
  • デプロイ通知
    CodeDeploy+SNS+Lambda+Slack
    ArgoCD Notifications

  • デプロイ、リリース、巻き戻し手順書作成およびモブ作業
    手順を番号で示しどこまでやったか伝わるようにする   作ったあとはモブ作業して手順書を検証する

  • リリース対応のChatOps化
    AWSコンソール作業->ChatOps化で

    • 作業時間が1/4に
    • ミスが起きづらいように
    • 開発メンバーならでもリリースできるように
  • 障害対応のChatOps化 playbook表示

    • 新規メンバーでも何かしら動けるように
    • 対応状況をオープンに
  • 開発工程調整
    プロジェクト初期にprdまでのパイプラインを整備することで後半で問題が起こるのを防止

まとめ

チームの開発生産性をあげるために

  • 開発のフィードバックサイクルをいかに早くするか?
  • 再現性が高いか?
  • 持続可能か?
  • 認知負荷をいかに低くするか?

を考えて開発する

負荷試験改善

辛かったこと

  • 環境の起動に時間がかかる
  • 結果の収集に時間がかかる
  • モニタリングするものが多い
  • 作業負荷が集中する EC2インスタンスにログインし、手動でLocust実行

対策

自動化

  • ecs run-taskで実行
  • 結果はS3に保存&ローカルにDL

モブプロ・モブ作業

シナリオ作成のモブプロ
負荷試験モブ作業で他のメンバーも負荷試験できるように

段階的な負荷検証

  • ツールの検証
    CloudFront+S3
  • アプリケーション
    フレームワークの初期実装 実際のアプリケーション実装
  • データストア

参考『Amazon Web Services負荷試験入門』

まとめ

  • 負荷試験は定期的に行われるので自動化することを前提として開発する
  • ドキュメンテーションやモブ作業などを通して知識を共有する
  • アプリケーションが複雑になるとボトルネックの特定が困難になるので、問題を切り分ける

職能横断組織と機能別組織

職能横断組織...サービスの機能ごと 例)決済チーム、ログイン・会員登録チーム

機能別組織...職種ごと 例)フロントエンドチーム、バックエンドチーム

辛かったこと

  • 取引コストの増加
  • 責任と権限の不一致

  • 困りごと
    EC2たてるのに別チームに依頼(チケット発行)が必要 二週間後にEC2が出来上がる

  • 対策
    チームの目的に合わせた権限付与   dev環境は開発チームに操作権限を付与   開発チームがTerraformを書いてインフラチームがレビュー

  • 困りごと
    開発チームがアプリケーションエラーが出ていることに気づかない   SREがアプリケーションエラーを見つけて報告(内容がわからない)

  • 対策
    開発チームにモニタリングの習慣をつける   朝会で開発チームがアプリケーションログとAPMを確認する   開発チームにエラー通知

まとめ

チーム全体の開発速度を速くするために

  • 開発チームが自律的に開発する
  • 開発チームが自律的に開発できる体制を作る DevOps, (Embedded)SRE

組織デザイン、プロダクトの性質、プロダクトのフェーズ、によって最適解が変わってくる

プロダクト視点と専門的な視点は両方必要なので両方の強みを活かす

参考 『エンジニアリング組織論への招待』 『マイクロサービスアーキテクチャ』 『EffectiveDevOps』

まとめ

  • チームの開発生産性を支えるような活動をやってきた
    • 再現性の高い開発環境づくり
    • 継続的に開発できるチームづくり
      プラットフォームというプロダクトの特性上、開発期間は長い

ブログのまとめ

LTスライドを転記してみたら非常に読みづらかった。
今後も開発生産性とか、継続的に開発できるチームづくりは注力していきたい。

いい手順書とは

概要

私が思ういいWebシステム開発における手順書についてまとめる
前提

結論

いい手順書とは下記のような特徴がある

  • 手順に番号が振ってある
    番号が振ってあることで下記のメリットがある
    • 全体を見渡したときに手順がいくつあるかわかる
    • 何番までやったか、何番を実施中か数字で表現できる
  • 手順を実行したかどうか判断できるフィードバックがある
    作業者以外が作業を見たときにどこまでやったかわかる
  • 一つの手順に一つのアクションがある 複数の手順をまとめない
  • 冗長な表現をせず短い文章で書かれている
  • 複数の人間に使われ検証されている
    引き継ぐことを前提とし、複数人が利用する。
    複数人に使われることで手順書の穴が見つかるので、都度修正しブラッシュアップする。
  • 手順以外の情報が書かれていない
    解説や注釈などは別のスペースにまとめる。
    実行するアクションのみ書く。
  • 命令形を使わない
    これは知識が足りずうまく言語化できないが、下記のイメージ。
    • 動詞で終わる例:1. ボタンAを押す
    • 命令形の例:1. ボタンAを押すこと

まとめ

普段なんとなく意識している、自分がいいと思う手順書についてまとめた。
上記のようになっていない手順書をみて結局何をすればいいかわからなかったり、読みづらかったりしたのでまとめた。
手順が多く複雑な場合は自動化を検討したい。

関連情報

下記のエントリに似たような事が書いてあり、方向性が間違ってないことを確認した。 zenn.dev

GitHub ActionsからAWS CodeBuildをキックする

概要

GitHub ActionsからAWS CodeBuildをキックする方法を示す。

背景

  • すでにCodeBuildプロジェクトの資産がある
  • GitHubと連携しており、CodeBuildの結果をAWSコンソールを開いて確認するのが手間

結論

下記のようにして実装した。
RoleとCodeBuildプロジェクトをinputsとして渡すことで汎用的にした。

使っている技術

hoge/.github/workflows/my-workflow.yml

name: my workflow

on:
  pull_request:

jobs:
  call-workflow-run-code-build:
    permissions: # 呼び出し元でpermissionsを宣言すれば呼び出し先でOIDC ProviderでAWSにアクセスできる
      id-token: write
      contents: read
    uses: tom-256/my-workflow/.github/workflows/run-codebuild.yml@main
    with:
      roleToAssume: arn:aws:iam::<ACCOUNT_ID>:role/aws-codebuild-run-build-role
      codeBuildProjectName: my-codebuild-project

Roleは用途に合わせ適宜権限を絞る。

my-workflow/.github/workflows/run-codebuild.yml@main

name: run codebuild

on:
  workflow_call:
    inputs:
      codeBuildProjectName:
        required: true
        type: string
      roleToAssume:
        required: true
        type: string

jobs:
  run-code-build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ inputs.roleToAssume }}
          aws-region: ap-northeast-1
      - name: Run CodeBuild
        uses: aws-actions/aws-codebuild-run-build@v1
        with:
          project-name: ${{ inputs.codeBuildProjectName }}

aws-codebuild-run-build-roleのポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "codebuild:StartBuild",
                "codebuild:BatchGetBuilds"
            ],
            "Resource": "arn:aws:codebuild:ap-northeast-1:<ACCOUNT_ID>:project/<CODEBUILD_PROJECT_NAME>"
        },
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": "logs:GetLogEvents",
            "Resource": "arn:aws:logs:ap-northeast-1:<ACCOUNT_ID>:log-group:/aws/codebuild/<LOG_GROUP_NAME>/:log-stream:<LOG_STREAM_NAME>/*"
        }
    ]
}

aws-codebuild-run-build-roleのTrusted entities

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:<ORG>/*:*"
                }
            }
        }
    ]
}
"repo:<ORG>/*:*"

の箇所はreusable workflowを使ったのでワイルドカードを指定することで特定のリポジトリによらず利用できる。

GitHub ActionsのTips

これらを書くときに知ったGitHub ActionsのTips

  • permissionはreusable workflowの呼び出し先にも適用される

https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions

下記のようなエラーが出る

invalid value workflow reference: workflows must be defined at the top level of the .github/workflows/ directory

まとめ

  • aws-actions/aws-codebuild-run-buildを使うとGitHub ActionsからAWS CodeBuildをキックできる ただし、GitHub Actionsを経由している分実行終了が遅い(移行時に同時実行していてわかった)
  • 既存資産があってGitHub Actionsに移行する予定がある場合は、段階移行の手段としても使えそう
  • CIのトリガーはGitHub Actionsのほうが柔軟なので、トリガーはGitHub Actions、処理は別のCIサービスと分けることができる
  • CircleCI-Public/trigger-circleci-pipeline-actionでも同じものがあった

workflow dispatchでnpm versionを更新する

概要

  • ある変更の塊に対してタグを付けたい
  • ローカルでやると間違えるのでCIで統一したい
  • tagとreleaseを分けたい

コード

.github/workflows/tagging.yml

name: tagging

on:
  workflow_dispatch:
    inputs:
      version:
        type: choice
        description: "patch | minor | major"
        required: true
        default: "patch"
        options:
          - patch
          - minor
          - major
    branches: [main]

jobs:
  tagging:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
      - run: |
          git config --local user.name "GitHub Actions"
          git config --local user.email "actions@github.com"
          NEXT_VERSION=$(npm version ${{ github.event.inputs.version }})
          git add package.json package-lock.json
          git push origin ${NEXT_VERSION}

動いている様子

f:id:mMQnaZ7vL2DWkoU:20220114122153p:plain

f:id:mMQnaZ7vL2DWkoU:20220114122224p:plain

f:id:mMQnaZ7vL2DWkoU:20220114122931p:plain

まとめ