メインコンテンツまでスキップ

Software Design 202311

個人開発成功の必須条件

最速で開発・リリースして需要を見極める

  • 個人開発のメリット
    • モダンな技術を使える
    • ソフトウェア開発の全体像が見える
    • やりたいことをやれる
  • 失敗しないためには
    • 先に集客&マネタイズ
      • Airbnb はサービス開発する前に自分たちのオフィスをレンタルして本当に人が来るか見極めたらしい
      • インスタで DM 送って使ってもらって課金してくれそうか様子を見るとか
      • Google フォームで事前登録をしてもらい、連絡先を確保したうえ、課金してでも使いたいか聞いておくとか
    • 休日 2 日〜3 日で開発してリリース
      • コアのコアのコアだけを開発する
      • 99%は失敗するので、正しい設計とか、スケールできる構成とか、自由度の高い設計とか、利益出てから考えるで十分
      • 最初はほとんど手動でもいい、後で自動化する
    • お金をかけない

長期的視点でスモールビジネスを続ける

  • 個人開発の良さ
    • 得た経験を本業に活かせる
    • 次の仕事につながる
    • すべての工程を体験できる
    • 自分のサービスが使われる感動を得られる
  • 作りきれない問題の原因と対策
    • 構想が大きすぎる
      • まずは 1 ヶ月で作れるものを
      • 要件は削りまくってまずは MVP を作る
      • なる早でユーザーに使ってもってフィードバックを得る(恐れるな)
    • 技術的なハードルが高すぎる
      • 未経験な技術は使っても 1 つまで
      • 技術習得とサービス開発はわけて考える
        • 技術習得している段階では進捗がないように見えて病むので
      • まずは動かすことが最優先
        • リファクタは後でやる
        • 動的な部分であっても一旦は静的にしておく
        • PC だけ、iOS だけ、などデバイスを絞る
        • CSS を一切書かない、あとで書く
        • 外注する (Consumer Generated Media 等の場合は人が作ったデータがあるだけで完成度が違う)
    • 先に似たサービスが出てしまった
      • 気にしないでいい or 目に余る場合はピボット
    • 誰にも使われなかった
      • 自分でとにかく使う
      • 自分がほしい部分をとにかく作る
      • 恥を捨てて宣伝する
      • 1 人でも使い続けるモチベーションがある仕組みにする
  • 金銭的成功を目指すためには
    • スモールビジネスをやる not スタートアップ
    • お金のかからない構成にしておく
    • 波が来るまでじっくり待つ
    • アップデートをし続ける(これできてないひと多いよ!必須よ!)
    • 他のことを捨てて時間を作る
    • 収益を出せる前提のサービスにする(無料プランはやらない、とか)

1 つのアプリをじっくり育てる

  • 仕事の種類
    • フロー型の仕事 / 人月の切り売り / 受託
    • ストック型の仕事 / お金を生み出す仕組みに投資する / 個人開発
  • サービスを軌道に乗せるには
    • ヘルプや初回起動時のガイドを作る
      • 最大の敵は「わからない」
    • 企業を超えたウェットなサポート
    • 無料機能と有料機能のバランスをよく考える
      • サブスクに一切お金を払わない日本人ばかりだったのは 10 年前の話
      • 広告ほど不安定な収入源はない
    • 新規アプリ作成に逃げずじっくり育てる
    • 辛辣なレビューに向き合う
      • ちゃんと直せば星 5 になおしてくれたりするよ
  • 個人開発の成功とは、他人の評価軸に左右されず、自分のやりたいことをやり続けられること

理想のコンテナイメージを作る

理想のコンテナを目指すための基礎知識

  • Docker コンテナ
    • 完全にサンドボックス化されたプロセス
    • カーネルを共有しているため、コンテナとホストマシンで以下を一致させる必要がある
      • OS(Linux/Windows)
      • CPU アーキテクチャ(x86/Arm)
  • Docker イメージ / コンテナイメージ
    • コンテナのひな形である
    • 読み取り専用である
    • Dockerfile をもとにビルドして作られる
    • 仮想マシンイメージと比べてはるかに軽量
    • ファイルシステムとメタデータから構成される
      • ファイルシステムの作成は、ベースイメージというもととなるイメージ起点に、必要なアプリケーションやライブラリ等を段階的にインストールしていくことで行う
      • メタデータとは、環境変数、デフォルトコマンド、ポート番号など
    • イメージレイヤの集合体である
      • Dockerfile に書かれた各手順ごとにイメージレイヤが作成される
      • 複数の親子関係を持つレイヤ群が、最終的にあたかも 1 つのファイルシステムのように振る舞う
      • 容量やセキュリティの観点からレイヤは少ない方がいい
  • Dockerfile
    • コンテナイメージを構築するための設計図
    • ツールに寄っては Conteinerfile という名前であることもあるが、書ける命令は同じ
  • 理想的なコンテナ
    • 再現性
      • バージョンは固定する
    • セキュリティ
      • 非特権ユーザで動作させる
      • 不要なファイルを含めない
        • 特にクレデンシャルファイルには要注意
        • マルチステージビルドにより成果物のみ共有するのが有効
      • distroless にする
        • Go や Rust などバイナリが 1 つ動くものであれば使える
      • 頻繁にビルドし直す
        • セキュリティパッチが適用されているかもしれないので
        • --no-cacheを忘れずに
    • 可搬性
      • 環境依存をなくす
      • コンテナイメージを軽量に保つ
  • DockerFile の書き方おさらい
    • (前半省略)
    • パーサディレクティブ
      • syntax
        • Dockerfile のバージョンを指定する
      • escape
        • エスケープ文字を指定する
  • Dockerfile の記述で気をつけること
    • ベースイメージの選択
      • 軽量でセキュアなものを選ぶ
      • 公式イメージや認定パブリッシャーのイメージが無難
        • Docker Official Image / Verified Publisher / Sponsored OSS のバッジがあるもの
      • アプリと同じプログラミング言語のランタイムがあらかじめ含まれたものを選ぶ
      • alpine は良い選択だが、無思考で選んでいいわけじゃないので注意
        • 以下に起因するトラブルが多い
          • sh を始めとするコマンド群の実態が BusyBox の単一ライブラリである
          • C ライブラリが glibc でなく musl である
        • Ubuntu もいうほどデカくないぞ
      • 選択できるなら distroless を選ぶ
        • いくつか種類があるので適したものを選ぶ
          • シェルがないと問題発生時にデバッグで死にそうな場合は debug タグの付いたものを選べ
    • イメージサイズ・レイヤ数
      • ライブラリをインストールする際は後でキャッシュを消すか、そもそもキャッシュを作らせないようにする
      • マルチステージビルドによりセキュリティの向上、サイズとレイヤ数の削減ができる
    • ビルドの速度
      • 変更の少ない命令を先に書くことでキャッシュを効かせる

Dockerfile のベストプラクティス

  • ビルドコンテキスト / 構築に必要な文脈情報 の種類
    • ファイルシステムコンテキスト
      • Dockerfile を含む(ローカルのディレクトリ | Git リポジトリ | tar ファイル)などをもとにイメージをビルドする
      • COPY や ADD が行える
    • テキストファイルコンテキスト
      • 標準入力ストリームやリモートのテキストファイルをもとにイメージをビルドし、ファイルシステムは一切使わない
  • ガイドライン・推奨事項
    • エフェメラルであること
      • 常に捨てられる状態であれ
      • ステートフルなアプリをそのままコンテナにするな
    • 公式イメージ・認定パブリッシャーのイメージを使う
      • 容量とセキュリティの観点から
    • ビルドコンテキストを減らす
      • ビルド時に意図しないメモリ浪費を防ぐため
      • .dockerignoreに書く
    • 不要なパッケージを入れない
      • apt-getするときに--no-install-recommendsするとか
    • アプリケーションを分離する
      • ただし 1 コンテナ 1 プロセス原理主義はまちがい。場合によるし必須でもない。
    • レイヤを少なく保つ
      • ただし、昔と違って今の Docker Engine で実容量に影響を与えるコマンドは RUN,COPY,ADD のみなので、そこだけ気をつければ OK
    • 引数を並べ替えて書く
      • \や Heredoc を使って複数行に分けて書くことで保守性が高まる
    • 標準入力とパイプの使用
      • (どうしても Dockerfile 作りたくないときにいいかも。よくわからんので省略)
  • 命令ごとのベストプラクティス
    • LABEL
      • \を使うなどしてまとめて 1 行で書く
    • RUN
      • なるべくライブラリのインストールが不要なイメージを選ぶ
      • apt-get updateapt-get installは同時利用する
        • 意図しないキャッシュが効いてしまうため
      • キャッシュの削除をセットで行う
    • CMD / ENTRYPOINT
      • 組み合わせて使い勝手を良くする
        • CMD しかない場合
          • デフォルトのコマンドと引数として扱われる
        • 両方ある場合
          • ENTRYPOINT が必ず実行されるコマンドと引数として扱われ、CMD はデフォルトの引数として扱われる
          • e.g. 何も指定しなければ ENTRYPOINT + CMD として実行される
    • ADD / COPY
      • セキュリティ上の観点から COPY を使え
      • ADD はリモートの URL も指定でき、何が入っているかわからないので危険
    • WORKDIR
      • Dockerfile 内では常に WORKDIR からの絶対パスで書くと読みやすい
    • ONBUILD
      • 使うな。マルチステージビルドを使え。

ベースイメージの選び方

(前述)

コンテナイメージ作成に役立つツール

  • Docker Deesktop
    • Docker Scout
      • 脆弱性診断
    • SBOM 出力
  • Docker Deesktop 機能拡張
    • Aqua Trivy
      • 脆弱性診断 / SBOM 出力 / CLI もある
    • Dive In / ファイルサイズの分析改善 / CLI もある
  • VSCode 機能拡張
    • Hadolint / Dockerfile の Lint ツール / CLI もある

コンテナイメージのセキュリティ

  • 脆弱性の種類と対策
    • イメージに含まれる脆弱性
      • Critical と判定されても、使い方によっては問題ない場合もあるので留意
    • イメージビルド時の不備
      • 信頼できないビルドイメージは利用しない
      • 機密情報のコピー
        • マルチステージビルドにする
        • RUN 命令で--mount-type=secretを使う
    • 前述の問題はいずれも Trivy により検出が可能
  • イメージのセキュアな運用
    • latest タグを使用しない
      • 勝手に壊れるのを防ぐため
      • ハッシュ値での指定が推奨されている
        • Docker レジストリでは同じバージョンで再プッシュができるので(マジか!)
    • レジストリをセキュアに設定する
      • IP 制限
      • 再プッシュ禁止

Go 言語のエラー処理 (後編)

  • カスタムエラー構造体の命名
    • XxxError という名前にするのが Go の慣習
type XxxError struct {
Msg string
File string
Line int
}

func (e *XxxError) Error() string {
return fmt.Sprintf("%s:%d: %s", e.File, e.Line, e.Msg)
}
  • 事前定義エラー変数の命名
    • ErrXxx という名前にするのが Go の慣習
    • エラー文字列
      • 原則小文字で書く
      • 句読点は入れない
      • パッケージ名を入れておくとよい
// errors.New()はシンプルに文字列からエラー変数を生成するときに使える便利な関数
var ErrNotFound = errors.New("not found")
  • 値とエラーを混ぜない
    • Go は多値返却が可能なので、処理結果とエラーを同一の変数で返す必要性がない
  • nil と型あり nil を使い分ける
    • err != nil で判定したときに、型あり nil は常にtrueとなってしまう
    • Go 初学者がやりがちなミス
// 明示的なnil
return nil

// 型ありnil
var e *MyError = nil
return e
  • defer 文の中で発生したエラーの扱い方
    • 以下の 2 つの組み合わせでうまく処理できる(詳細は本文参照)
      • 名前付き戻り値の仕組みを使う
      • 本筋の処理で発生したエラーが、defer 文内で発生したエラーで上書きされないよう、errors.Join()を使ってエラーのマージ等を行う
  • エラーのラップと判定
    • エラー A の原因となったエラー B という因果関係を表現するために、Unwrap()メソッドによるエラーのラップを行う場合がある
    • errors.Is()
      • ラップされたエラーをUnwrapし続けると、最終的に判定対象のエラーと同じ型に到達するかどうかを判定する
    • errors.As()
      • error.Is()と同等の判定を行うのに加え、マッチした場合にはその値を取り出すことができる

AWS 活用ジャーニー / Amazon GuardDuty

  • GuardDuty とは、モニタリングにより不審なアクティビティを検出するサービス
  • モニタリング対象
    • VPC フローログ (通信)
    • DNS ログ (通信)
    • AWS CloudTrail イベント (コンソール操作履歴)
  • 既存リソースに影響を与えず独立して動作することが保証されている
  • オプションで 5 つの保護プランがあり、任意にオン・オフできる
    • Malware Protection
      • EC2 やコンテナのマルウェアスキャン
    • EKS Protection
      • 監査ログのモニタリング
    • S3 Protection
      • Get, List,Delete などデータイベントのモニタリング
    • Lambda Protection
      • 不正サーバとの通信検出
    • RDS Protection
      • 学習期間を経た後、イレギュラーな脅威を検出できるようになる

AWS のクラウドセキュリティ / ガードレール型運用

  • 種類
    • 予防的統制:ルールからの逸脱を防ぐ
    • 発見的統制:ルールからの逸脱を検知する
    • 訂正的統制:ルールからの逸脱を修正する
  • ゲートキーパー型 vs ガードレール型
    • ゲートキーパーは承認を挟むので業務が泊まる
    • ガードレールは走り続けられるので業務が止まらない
  • 予防的ガードレールの作り方
    • 以下のようなものを使う
      • Service Control Policy (SCP)
        • IAM ユーザー単位ではなく、Organizations のアカウントや OU 単位で権限を一元管理する
      • Permissions Boundary
        • IAM ユーザーやロール単位で権限を一元管理する
        • 難しいらしい