SPAアプリケーションのコンテナ化と設定値について

概要

コンパイルされたJavaScriptのSPAアプリケーションがある。
これはDockerコンテナ上で動いている。
このアプリはdev,stg,prdなどの環境ごとに異なるAPIエンドポイントを持つ。
この環境ごとに異なる設定値をどのように扱うか。

問題点

フロントエンドで実行されるJSファイルに設定値が含まれているので、コンテナ実行時に環境変数として渡すことができない。

結論

各方針のデメリットと現状の要件を照らし合わせ適切な方法を選択する。

方法一覧

案1

環境ごとにアプリケーションををビルドする。 実行時にどのバンドルを実行するか指定する。

デメリット

ビルドに時間がかかる。 イメージの容量が大きくなる。

下記にNext.jsの具体例を示す。

Dockerfile

FROM node:alpine as builder

WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci \
    && APP_ENV=development npm run build \
    && mv /usr/src/app/.next /usr/src/app/.next_development \
    && APP_ENV=staging npm run build \
    && mv /usr/src/app/.next /usr/src/app/.next_staging \
    && APP_ENV=production npm run build \
    && mv /usr/src/app/.next /usr/src/app/.next_production \
    && npm ci --production
COPY . /usr/src/app/

FROM node:alpine as runner
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app/.next_development /usr/src/app/.next_development
COPY --from=builder /usr/src/app/.next_staging /usr/src/app/.next_staging
COPY --from=builder /usr/src/app/.next_production /usr/src/app/.next_production
COPY --from=builder /usr/src/app/node_modules /usr/src/app/node_modules
COPY package*.json ./
COPY start.sh ./

CMD ash -c "./start.sh -d && ./node_modules/.bin/next start"

start.sh

#!/bin/ash
ln -s /usr/src/app/.next_${APP_ENV}/ /usr/src/app/.next

案2

環境ごとの設定ファイルを作り、実行時にフロントエンドから取得する。
具体的なイメージは参考リンク参照。
参考:https://www.barrydobson.com/post/configure-spa-docker/

デメリット

ユーザが設定を取得するためのリクエストを送らなければならない。

案3

環境ごとにDockerイメージをビルドする。

これはアンチパターンなので採用しない。
理由

  • ある環境で保証されていたことが別の環境では保証されなくなる
  • 開発が進むに連れ環境ごとの差分が発生する

参考:https://codefresh.io/containers/docker-anti-patterns/

案4

コンテナ実行時にビルドする。

デメリット

  • Dockerのライフサイクルとアプリケーションのライフサイクルがことなる
  • コンテナ実行時にビルドが失敗してコンテナが起動できない可能性がある

ライフサイクルがことなることで他のアプリケーションとの違いを意識する必要がある。メトリクス計測に影響が出る。
ビルドに成功したDockerイメージのみをDockerレジストリに登録する。

案4.5

コンテナ実行時に再ビルドする

  • docker build時にnpm run build
  • docker run時にnpm build && npm start

デメリット

上記と同じくDockerのライフサイクルとアプリケーションのライフサイクルがことなる

Goの開発tips

自分用メモ。

go test ./... -coverprofile=coverage.out && go tool cover -html=coverage.out

ShellCheckでSC1090: Can't follow non-constant source. Use a directive to specify location.が出たときの対処法

概要

super linterをGitHub Actionsで使っていてShellCheckで下記エラーが出た。

SC1090: Can't follow non-constant source. Use a directive to specify location.

super linterバージョン
linter.yaml

        uses: docker://github/super-linter:v3

https://github.com/github/super-linter

ローカル検証時のバージョン

$shellcheck --version
ShellCheck - shell script analysis tool
version: 0.7.1
license: GNU General Public License, version 3
website: https://www.shellcheck.net

原因

https://github.com/koalaman/shellcheck/wiki/SC1090

ShellCheck is not able to include sourced files from paths that are determined at runtime. The file will not be read, potentially resulting in warnings about unassigned variables and similar.

ShellCheckは静的解析ツールなので動的に実行時に動的に決まるディレクトリを解析できない。

解決方法

方法1

代わりに読み込める固定のパスを書く。パスが読み込めなければエラーになるので注意。

# shellcheck source=src/util.sh
. "${util_path}"

方法2

無効にする。

# shellcheck source=/dev/null
"${util_path}"

方法3

無効にする。下記のように特定のルールをdisableすることも可。

#shellcheck disable=SC1090
"${util_path}"

参考

ShellCheckのインストール方法

brew install shellcheck

Gitで WARNING: UNPROTECTED PRIVATE KEY FILE!が出たときの対処法

概要

git cloneしようとしたら下記のようなエラーが出る。

$git version
git version 2.26.0
Cloning into 'hoge'...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/Users/hoge/.ssh/hoge_id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/Users/hoge/.ssh/hoge_id_rsa": bad permissions
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

解決方法

グループとその他に対して実行以上(1以上)の権限がある場合に発生する。 今回の場合0644なので0600に変更すると解決する。

$chmod 600 /Users/hoge/.ssh/hoge_id_rsa

https://en.wikipedia.org/wiki/Chmod

機能組織について調べるために読んだ本まとめ

概要

機能組織と職能組織について調べたので文献をメモ。

まとめ

  • 機能組織は外部とのやり取りを減らし意思決定が早くなる
  • 職能組織は専門性を深める
  • 現代のWeb業界のソフトウェア開発だと機能組織がベター
  • 機能組織と職能組織をかけ合わせ相乗効果を狙う

『エンジニアリング組織論への招待 ~不確実性に向き合う思考と組織のリファクタリング

Chapter5技術組織の力学とアーキテクチャで組織構造とチーム間の「取引コスト」や職能横断組織について詳しく書かれている。

ドラッカー名著集14 マネジメント[中]』

45章で職能別組織とチーム型組織(機能組織)について語られている。

『マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

  • 作者:Sam Newman
  • 発売日: 2016/02/26
  • メディア: 単行本(ソフトカバー)
10章コンウェイの法則とシステム設計でフィーチャーチーム(機能組織)について語られている。

雑感

責任と権限について

例えば、あるアプリケーションを作る上で開発チームが、サーバ等のリソースをインフラ部に依頼して作ってもらうような場合、責任と権限が分離していると言える。 アプリケーションに対する責任があるのに、作業する権限がないという状態になる。
責任と権限の分離を防ぐために、アプリケーションを作ると決まったらそれに必要なスキルを持ったエンジニアをアサインしてチームを作る。

職能別組織の必要性について

Web業界はジョブ型雇用であるケースが多い。エンジニアとしてのキャリアパスを定める上で職能別組織が機能する。
また、専門知識が必要な仕事を集約・抽象化し、それを横展開するケースでも職能組織のほうが適している。 一人のエンジニアは一つ以上の機能組織と一つの職能組織に所属するようなイメージだと理解した。

正解は組織ごとにある

目的は事業を成功させること、仕事を正確に早く終わらせることであり、それらが達成されていれば形に拘る必要はない。
また、組織の課題は組織ごとの固有のものなので他社の事例や書籍の事例が100%適用されるわけではない。

DevOpsを推進するうえでチェックした資料まとめ

概要

最近DevOpsを推進する活動をしていたので、チェックした資料をまとめる。

『Effective DevOps』

Effective DevOps ―4本柱による持続可能な組織文化の育て方

Effective DevOps ―4本柱による持続可能な組織文化の育て方

DevOpsの文化、概念について書かれている。
組織が陥りがちなアンチパターンについても網羅されている。
エンジニアとして働いているとついツールや手法の方に目が行きがちだが、文化や概念を抑えておくと、手段と目的が逆になるのを防げてよい。

AWS

AWSでは下記ページでDevOpsの概念からツールまで一通り網羅されている。
DevOps とは? - DevOps と AWS | AWS
AWSではグラレコを用いた資料があるので、視覚的にわかりやすく、非エンジニアに説明するときに参考にすると良い。
AWS でデプロイの自動化を実現するベストプラクティスをグラレコで解説 - builders.flash☆ - 変化を求めるデベロッパーを応援するウェブマガジン | AWS
定期的に更新さているブログもある。
AWS DevOps Blog

GCP

GCPの資料ではアーキテクチャについて言及されている。
また、DevOps Research and Assessment(DORA)のレポートやホワイトペーパーがまとまっている。
What is DevOps? Research and Solutions  |  Google Cloud
GCPはかんたんな技術的チェックに答えると、業界平均と比べどの程度か把握することができる。
DORA DevOps Quick Check

Azure

Azureはチェックリストを公開している。
ここでも文化のほうが先に書かれており、ツールだけの話でないことが表されている。
DevOps とは何ですか?DevOps の説明 | Microsoft Azure
DevOps checklist - Azure Design Review Framework | Microsoft Docs
また、『Effective DevOps』やDevOpsに関するレポートがダウンロードできるようになっている。
Azure DevOps Services | Microsoft Azure

『Release It!』

この書籍にはDevOpsという言葉は出てこない。
しかし、ソフトウェアをリリースするにあたり気にしたほうがいいことについて、豊富な実例とともに書かれている。
実装、設計、運用と実際のソフトウェア開発の内容にフォーカスしている。
内容は少し古いが読み物として面白かった。

まとめ

DevOpsに関して、複数の資料を読むと共通して書かれている大事なことが見えてくる。
特にツールに関しては見知ったものが多かったが、組織活動・文化の重要性を説いた資料が多かった。 今回あげたものの中で上から『Release It!』を除いた4点の資料をおさえておくと良い。

Goのドット3つ

概要

Goを書いているときに見るドット3つについて公式ドキュメントのリンクをもとにコード例を示す。

配列

https://golang.org/ref/spec#Composite_literals

The notation ... specifies an array length equal to the maximum element index plus one.

とあるように下記の例だとdaysのインデックスが[0,1]であり、最大のインデックス1に1を追加して2となる。

package main

import (
    "fmt"
)

func main() {
    days := [...]string{"Sat", "Sun"}  
    fmt.Println(len(days)) //2
    arr := [2]string{"foo", "bar"}  
    fmt.Println(len(arr))  //2
}

可変長引数

https://golang.org/ref/spec#Passing_arguments_to_..._parameters
複数の引数を渡したいときは下記のように書く。

package main

import (
    "fmt"
)

func main() {
    Greeting("nobody")                          //nobody
    Greeting("hello:", "Joe", "Anna", "Eileen") //hello:Joe Anna Eileen
    s := []string{"James", "Jasmine"}
    Greeting("goodbye:", s...) //goodbye:James Jasmine
    // Greeting("goodbye:", s) //cannot use s (type []string) as type string in argument to Greeting

}

func Greeting(prefix string, who ...string) {
    fmt.Printf("%s", prefix)
    for _, val := range who {
        fmt.Printf("%s ", val)
    }
    fmt.Println()
}

スライスの追記とコピー

https://golang.org/ref/spec#Appending_and_copying_slices

package main

import (
    "fmt"
)

func main() {
    s0 := []int{0, 0}
    s1 := append(s0, 2)              // append a single element     s1 == []int{0, 0, 2}
    s2 := append(s1, 3, 5, 7)        // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}
    s3 := append(s2, s0...)          // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
    s4 := append(s3[3:6], s3[2:]...) // append overlapping slice    s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}
    fmt.Println(s4)

    var t []interface{}
    t = append(t, 42, 3.1415, "foo") //                             t == []interface{}{42, 3.1415, "foo"}

    var b []byte
    b = append(b, "bar"...) // append string contents      b == []byte{'b', 'a', 'r' }

}