landrunner’s blog

しばらく開発から離れてた人間が、技術的キャッチアップを図るための勉強ブログ

nodejsのプロジェクトをarm環境向けにクロスコンパイル

タイトルは半分嘘ですが。

やりたいこと

自分の作ったnodejsのプロジェクトを実行環境にデプロイしたい。
ただし、実行環境はRaspberry Piであり、アーキテクチャはarmかつOSは諸事情から32bit版しか使えない、 更に一部の依存モジュールはC++で記述されておりアーキテクチャに合わせてコンパイルが必要、という状況です。

もちろんRaspberry Pi上でnpm installすれば済む話ではあるのですが、npm installは非常に負荷が高くしばらくCPUを占有します。
なので可能であれば、CI等で依存モジュールを全て揃えて配布し、Raspberry Piでは実行するだけの状態にできることが理想です。

大抵の場合CIはx64環境で動作させると思いますので、x64環境からarm 32bit環境で動作させられるnode modulesを構築することが今回やりたいことです。

問題

要はC++のコードをarm 32bitで動かせるようにするということなので、npm install時にクロスコンパイラが呼び出されるようにすれば何とかなるかと思ったのですが、 モジュールによってはJSのコードの中でos.arch()os.platform()を参照してコンパイルするかどうか決めるのでこの方法は使えないことに気が付きました。 (具体的にはaws-crtがそういうモジュールで、主要環境にはコンパイル済みのオブジェクトコードが配布され、そうでない環境でのみコンパイルが走ります)
これを依存モジュールすべてで確認するのはかなりつらいです。

やり方(Linux限定)

Dockerでarm32bit環境向けのコンテナを動かしそこでビルドします。 まず以下のコマンドでインストールすると、QEMUというエミュレータの機能で他の環境向けコンテナが動かせるようになります。

sudo apt-get install qemu-user-static 

ちなみにDocker Desktop環境だと最初からエミュレーション環境が整っているので、この操作は必要ありません。

あとは実行時にplatformを指定すれば他環境向けのコンテナが起動します。
以下のようにnodeのプロジェクト直下に移動して、volume指定もすればそのままほしい環境でのnode_modulesが生成されます。

cd path_to_your_project
docker run --rm -v /$(pwd):/work/ --platform linux/arm/v7 -it node:18 /bin/bash
#ここからはコンテナ内
cd /work
npm install

更にpackage.jsonに以下の記述をしておくと、

"bundleDependencies" : true

npm packコマンドでこのnode_moudlesを含めてプロジェクトがひとつのtar.gzに固められます。

これ用のDockerイメージを作った

こちらです。
https://hub.docker.com/repository/docker/landrunner1/nodepack

イメージ作成用のファイルはGitHubにあります。

github.com

使い方は簡単でプロジェクト直下で以下のようにプラットフォームを指定して実行することで、 npm installした後にnpm packを実行し、tar.gzのファイルを作成してくれます。

docker run -v /$(pwd):/work --platform linux/arm/v7 --rm -t landrunner1/nodepack:18

VPN中にWSL2がネットワークに繋がらない問題に、Docker上でプロキシを作って対処する

VPNにつないだ時、VPNWindowsのルーティングテーブルをいじってWSL2がネットワークに繋がらなくなります。

なので解決方法としては、こちらもうまいことルーティングテーブルをいじってやる、というのが正攻法です。
こちらのブログで紹介されているやり方はまさにそんな方法です。
VPN に繋ぐと WSL2 や Hyper-V VM でネットワークに繋がらなくなる問題を解消する | Aqua Ware つぶやきブログ

この方法で解決できるならば、こちらを使うのがスマートだと思います。

自分の問題点

前述の方法はRoute Metricを60000に設定するのですが、私の環境だとVPNソフトウェアの仕様なのか、管理者の設定なのか、 VPNのRoute Metricを2以上に設定しようとすると強制的に1に戻されます。
なのでこの方法が使えませんでした。

ちなみに自分のVPNクライアントはCisco AnyConnect。ネット上でみる限り問題になっているのはほとんどこいつみたいです。
他のVPNクライアントはもう少しお行儀がいいのかもしれません。

Docker Desktop for Windows上にプロキシを作成

Docker Desktop for WindowsにはVPNパススルー機能といって、VPN中でも問題なくネットワークに接続できます。 そしてDocker Desktop for Windowsは設定一つでWSL2からもアクセスできるにようなります。
じゃあDocker Desktop for Windows上にSquidでプロキシを立てれば動くのでは?
ということで作ってみました。それがこちら。
GitHub - landrunner/vpn-wsl2-proxy

想定構成としてはこんな感じで、SquidVPNと社内のプロキシ経由でインターネットにアクセスします。

今回作ったのはこのsquidのコンテナになります。

使い方

Windows 10でWSL2はインストール済みという前提で記述します。

  • まずDocker Desktop for Windowsをインストールします。

https://hub.docker.com/editions/community/docker-ce-desktop-windows

※このブログを書いた当時、Docker Desktopはまだ法人でもフリーでも使えたのですが、2023/6/8現在一部を除き有料プランへの加入が必要になっています。 企業所属の方は必ず有料プランへご加入をお願いします。

  • 今回作ったコンテナのbuildファイルをgitで持ってきます
git clone https://github.com/landrunner/vpn-wsl2-proxy
  • docker-compose.yamlを開いてプロキシ設定を編集します。ここに設定するのが社内プロキシのホスト名とポートです。
    environment: 
      - PROXY_HOST=your_company_proxy_host
      - PROXY_PORT=8080
  • 起動します
docker-compose up -d
export http_proxy=http://localhost:3128/
export https_proxy=http://localhost:3128/
  • 動作確認します
curl https://www.google.com/

これで動けば設定は完了です。

VPCと送信元IPアドレスに基づいたアクセス制御のためのIAMポリシー

IAMユーザーのアクセス制御

AWSのIAMユーザーはIPアドレスを元にしたアクセス制御することで、会社など特定の場所からしかアクセスできないようにすることができます。(リンク先参照)

AWS: 送信元 IP に基づいて AWS へのアクセスを拒否する - AWS Identity and Access Management

これによりIAMユーザーのセキュリティを高めることができます。 ただ、1つ欠点があり、EC2上でAWSの機能を使おうとしたときもアクセスを拒否されます。
このポリシーが適用される限りCodeCommitからgit cloneをするといったことはできなくなります。

"Bool": {"aws:ViaAWSService": "false"}AWS内から機能を呼び出しをできるようにするための条件なのですが、残念ながら現在のところEC2始めいくつかの機能からの呼び出しはこの条件の適用対象外になっています。

VPCを条件に用いたアクセス制御

IPアドレスに加えVPCもアクセス制御の条件に加えます。
つまり特定のIPアドレスから実行されるか、または特定のVPC内で実行されれば動作するような条件に書き換えます。

そのIAMポリシーはこちら。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Action": "*",
            "Resource": "*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "1.1.1.0/24"
                },
                "Bool": {
                    "aws:ViaAWSService": "false"
                },
                "StringNotEquals": {
                    "aws:SourceVpc": "vpc-e00000000"
                }
            }
        }
    ]
}

vpc-e00000000の所に開発に使用しているVPCのIDを入れればOKです。

複数のVPCで使用したい場合はこちらでいけるはずです(試してません)。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Action": "*",
            "Resource": "*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "1.1.1.0/24"
                },
                "Bool": {
                    "aws:ViaAWSService": "false"
                },
                "ForAllValues:StringNotEquals": {
                    "aws:SourceVpc": ["vpc-e00000000", "vpc-e00000001"]
                }
            }
        }
    ]
}

注意

私はいつもCodeCommitやDynamoDBを呼び出すとき、VPCにエンドポイントを作って使用するのですが、これらのサービスはエンドポイントを作らずに使うと一旦インターネットを経由します。
インターネット経由の場合にこの権限設定が動くかは未確認です。

VPCを作ってインターネットにアクセスできるようにする

一応メモ。今日ばかみたいにはまってしまった。
プロキシ経由でアクセスすると何が悪いか分からなくなるんです。

以上

責任共有モデルという言葉に違和感を感じる

責任共有モデルとは

クラウド関係の仕事をやっていると責任共有モデルという言葉が時々出てきます。
これはクラウドを使ったシステムのセキュリティやコンプライアンスについて、この部分はクラウド側で見るけどこっち部分はユーザで見てね、と責任の所在をはっきりさせたものです。
当然、クラウドベンダとしてはユーザの設計やアプリのせいで起きた事故の責任を負うことはできませんし、ユーザとしてはクラウド側のバグやエラーに対して責任を持てません。 こういう責任の分割が行われるのはよく理解できます。

aws.amazon.com

f:id:landrunner:20200625223410p:plain

(図は上記のリンクから引用)

この言葉の初出はよく知りませんが、調べると真っ先にAWSが出てくるのでAWSじゃないでしょうか?
責任共有だけで調べると金融関係のサイトがたくさんでてくるのでそちらが先かもしれません。

何に違和感を感じるか

共有という言葉と実際の定義が合ってないように見えるところです。
共有と書いているのに実際は責任の所在を分割しましょうという内容です。
クラウドとユーザで共有している責任はひとつもありません。 だったら責任分割とか責任分散でいいじゃないですか。

もちろん1つの大きな責任を共有してお互いの担当範囲を決めただけ、という解釈も可能ですが、 かなりこじつけ感があります。

責任共有という名前はどこから

これはshared responsibility の訳です。
shareは共有すると訳すので妥当に見えますね。



本当にそうでしょうか?

ここで今一度shareの意味を辞書で調べて見ましょう。

1.
a 〔+目的語(+out)〕〈…を〉分ける,分配する.
b〔+目的語(+out)+with+(代)名詞〕〈…を〉〔…と〕分ける.
c〔+目的語(+out)+前置詞+(代)名詞〕〈…を〉〔…の間で〕分け合う,分配する 〔among,between〕


2. 
a〈ものを〉共有する; 〈費用・責任などを〉分担する; 〈意見・苦楽などを〉共にする.
b〔+目的語+with+(代)名詞〕〈…を〉〔人と〕共有する,共にする; 〈…を〉〔人に〕語る,披露する.

確かに共有するという意味もあるのですが、分ける・分配するという意味の方がメインです。
考えてみると情報など減らないものを除き、shareというのは分けることを意味していることが多いように思います。

食べ物をシェアするということを食べ物を分けるといいますが、食べ物を共有すると言う人は多くないんじゃないでしょうか。

time-shared controlは時分割制御ですし、Secret sharing method は秘密分散法です。
「A社のマーケットシェアが30%」と言うときも、「市場全体を1と見て、そのうちの30%がA社に取り分けれている」ということですよね。

こういうふうにshare=分けると考えると
shared responsibility = 分割された責任
です。
この意味ならこのモデルにマッチしてますね。

結論

多分訳が悪いのだと思います。

この名前は5年くらい使われており今更この訳が改められることもないと思うので、
責任共有モデル=責任分割モデル
と頭の中で変換して乗り切りましょう。

WSL2ベースのdocker desktopのファイル配置位置

Cドライブがいっぱいになってきたので移せないか検討のため調べてみた。

Linux内のファイルへアクセス

#dockerが動作するlinux?
\\wsl$\docker-desktop

#dockerが実際にvolumeやイメージのファイルを置く場所
\\wsl$\docker-desktop-data\version-pack-data\community\docker

dockerの仮想ディスクの場所

"C:\Users\username\AppData\Local\Docker\wsl\distro\ext4.vhdx"

"C:\Users\username\AppData\Local\Docker\wsl\data\ext4.vhdx"

おまけ

Ubuntu-20.04の仮想ディスクの場所

"C:\Users\username\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu20.04onWindows_79rhkp1fndgsc\LocalState\ext4.vhdx"