自宅k8s環境向けにownCloudのマニフェストを書く

週末に自宅で動かしているownCloudを、Kubernetesに乗せるためにマニフェストを定義していた.
私用用途なのでPV使うまでもないかなとhostPathを使ったり、ラベルの指定もまばらで規則性がないので気が向いた時に直しておきたい.

マニフェスト

ira-apps/owncloudで定義しているマニフェストkustomize buildの出力結果は以下のような形です.

Ingress

ファイルアップロードがされるWebアプリなので、とりあえずclient_max_body_sizeを20GBに指定しています.

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-body-size: 20G
  labels:
    app.kubernetes.io/component: owncloud-server
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-server
    app.kubernetes.io/part-of: owncloud
  name: owncloud-server
  namespace: owncloud
spec:
  rules:
  - host: owncloud.local
    http:
      paths:
      - backend:
          serviceName: owncloud-server
          servicePort: http

Service

WebアプリとDBのDeploymentを分けている都合上、Serviceもそれぞれ定義しています.

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-server
    app.kubernetes.io/part-of: owncloud
  name: owncloud-server
  namespace: owncloud
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: http
  selector:
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-server
    app.kubernetes.io/part-of: owncloud
  type: ClusterIP

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: db
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-db
    app.kubernetes.io/part-of: owncloud
  name: owncloud-db
  namespace: owncloud
spec:
  ports:
  - name: mysql
    port: 3306
    protocol: TCP
    targetPort: mysql
  selector:
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-db
    app.kubernetes.io/part-of: owncloud
  type: ClusterIP

Secret

今回は載せませんが、SealedSecretを使って定義してます.
導入は多少面倒かもしれませんが、クラウドプロパイダを利用しない場合は手軽です.

Deployment

helm/chartsにあったownCloudのマニフェストを参考にしています.
PVCを利用してソフトウェアRAIDを組んでいるディスクに書き込みたかったんですが、サクッとKubernetsに移行しておきたかったので、hostPathで動かしています.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-server
    app.kubernetes.io/part-of: owncloud
  name: server
  namespace: owncloud
spec:
  selector:
    matchLabels:
      app.kubernetes.io/instance: owncloud
      app.kubernetes.io/managed-by: argocd
      app.kubernetes.io/name: owncloud-server
      app.kubernetes.io/part-of: owncloud
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app.kubernetes.io/instance: owncloud
        app.kubernetes.io/managed-by: argocd
        app.kubernetes.io/name: owncloud-server
        app.kubernetes.io/part-of: owncloud
    spec:
      containers:
      - env:
        - name: OWNCLOUD_DOMAIN
          value: localhost
        - name: OWNCLOUD_DB_TYPE
          value: mysql
        - name: OWNCLOUD_MYSQL_UTF8MB4
          value: "true"
        - name: OWNCLOUD_DB_HOST
          value: owncloud-db
        - name: OWNCLOUD_DB_NAME
          value: owncloud
        - name: OWNCLOUD_DB_USERNAME
          value: owncloud
        - name: OWNCLOUD_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              key: MYSQL_PASSWORD
              name: owncloud-db
        image: owncloud/server:10.3.2
        imagePullPolicy: Always
        livenessProbe:
          exec:
            command:
            - /usr/bin/healthcheck
          initialDelaySeconds: 5
          periodSeconds: 30
          timeoutSeconds: 10
        name: owncloud-server
        ports:
        - containerPort: 8080
          name: http
        readinessProbe:
          exec:
            command:
            - /usr/bin/healthcheck
          initialDelaySeconds: 120
          periodSeconds: 30
          timeoutSeconds: 10
        volumeMounts:
        - mountPath: /mnt/data
          name: owncloud-server
      volumes:
      - hostPath:
          path: /geminos/kubernetes/owncloud/server
        name: owncloud-server

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: db
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-db
    app.kubernetes.io/part-of: owncloud
  name: db
  namespace: owncloud
spec:
  selector:
    matchLabels:
      app.kubernetes.io/instance: owncloud
      app.kubernetes.io/managed-by: argocd
      app.kubernetes.io/name: owncloud-db
      app.kubernetes.io/part-of: owncloud
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app.kubernetes.io/instance: owncloud
        app.kubernetes.io/managed-by: argocd
        app.kubernetes.io/name: owncloud-db
        app.kubernetes.io/part-of: owncloud
    spec:
      containers:
      - args:
        - --default-authentication-plugin=mysql_native_password
        env:
        - name: MYSQL_DATABASE
          value: owncloud
        - name: MYSQL_USER
          value: owncloud
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              key: MYSQL_PASSWORD
              name: owncloud-db
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              key: MYSQL_ROOT_PASSWORD
              name: owncloud-db
        image: mysql:8
        livenessProbe:
          exec:
            command:
            - /usr/bin/mysqladmin
            - -u root
            - --password="${MYSQL_ROOT_PASSWORD}"
            - ping
          initialDelaySeconds: 5
          periodSeconds: 30
          timeoutSeconds: 10
        name: owncloud-db
        ports:
        - containerPort: 3306
          name: mysql
        readinessProbe:
          exec:
            command:
            - /usr/bin/mysqladmin
            - -u root
            - --password="${MYSQL_ROOT_PASSWORD}"
            - ping
          initialDelaySeconds: 5
          periodSeconds: 30
          timeoutSeconds: 10
        volumeMounts:
        - mountPath: /var/lib/mysql
          name: owncloud-db
        - mountPath: /var/lib/backup
          name: owncloud-db-backup
      volumes:
      - hostPath:
          path: /geminos/kubernetes/owncloud/db
        name: owncloud-db
      - hostPath:
          path: /geminos/kubernetes/owncloud/db-backup
        name: owncloud-db-backup

tmuxのステータスラインにk8sのvwntextとnamespaceを表示する

複数のk8sクラスタに接続する頻度が増えてきたので、現在どのコンテキストへ接続しているのかが分かりやすくtmuxのステータスラインに表示するようにしました.

f:id:corrupt952:20210127074407p:plain

この画像で言えば(kind-ira/owncloud)が、それぞれ現在選択するContextとNamespaceです.
実際の設定は、

set-option -g status-right "#[fg=colour33]#(/bin/bash ${HOME}/bin/tmux-kubectl) #[fg=green][#S:#I.#P]"

と定義し、スクリプトの実行結果をステータスラインに表示しています.
細かいケースを考慮できていないかもしれませんが、スクリプトは以下のように定義しています.

#!/usr/bin/env bash

if [[ -z "$(which kubectl 2>/dev/null)" ]]; then
    echo "kubectl doesn't exist"
    exit 1
fi

kubeconfig="$HOME/.kube/config"
if [[ -n "${KUBECONFIG}" ]]; then
    kubeconfig=${KUBECONFIG}
fi

context="$(kubectl config current-context 2>/dev/null)"
if [[ -z "${context}" ]]; then
    echo "current-context doesn't exist"
    exit 1
fi

namespace="$(kubectl config view -o "jsonpath={.contexts[?(@.name==\"${context}\")].context.namespace}" 2>/dev/null)"
[[ -z "${namespace}" ]] && namespace="default"

echo "(${context}/${namespace})"

今後、使っていく時に何か不都合があれば少しずつ改善していきますか.

Lambdaの関数名とランタイムの一覧を取得する

今朝、Lambdaの関数名とランタイムの一覧を取得するPythonスクリプト書いたので貼っておきます.
シェルの実装でも良かったが、これをベースに他のスクリプトを組む予定なのでboto3で実装してみました.

#!/usr/bin/env python3

import json
import boto3

def aws_region_names():
    ec2 = boto3.client('ec2')
    regions = ec2.describe_regions()['Regions']
    return sorted([regions[_]['RegionName'] for _ in range(len(regions))])

def lambda_functions(region_name):
    client = boto3.client('lambda', region_name=region_name)
    functions = client.list_functions()['Functions']
    return [{ 'Name': functions[_]['FunctionName'], 'Runtime': functions[_]['Runtime']} for _ in range(len(functions))]

if __name__ == '__main__':
    region_functions = {}
    for region_name in aws_region_names():
        region_functions[region_name] = lambda_functions(region_name)
    print(json.dumps(region_functions))

処理の流れは書くまでもないですが、

  • リージョン一覧取得
  • リージョンごとのLambda Function一覧取得

をしています.
処理自体は簡単だが、関数名とランタイムを抽出しているリスト内包表記は大分見づらいので改善の余地はありそうです.

某社SREとしての1年間

これはSRE Advent Calendar 20195日目の記事です.

一昨日、6Vメタモンを入手することができてホクホク顔の@corrupt952です.

技術的なことは他の方が書いてくれると思うので、某社SREとして過ごしてきた約1年間の話を簡潔に書きます.
SREに興味ある人や某社SREに興味ある人の参考になれば幸いです.

某社SREになった経緯

前職はソフトウェアエンジニアとしてSIerで働いていました.
私がSREになった理由は、「転職しました」に書いてある

自分に足りない知識を身につけ、自分の知識を元に挑戦したいという思いがありました.

を実現するための手段としてSREにチャレンジしてみたかったからでした.

SREやインフラに興味を持ったきっかけは @f440@yoppiの3人でISUCON5に出たのがきっかけでした.
あの時は結果として予選落ちだったのですが、自分にとってはアプリケーションを動かすために速く動かすために、アプリケーションコードだけでなくサービスアーキテクチャやインフラ側により目を向けていたので良いきっかけになりました.

そういった経緯もあって声をかけてもらった某社にSREとして入社することを決めました.

そもそもSREって何?

これに関してはネットに情報が溢れているので割愛しますが、各社によって実際の役割や責務が異なります.

某社におけるSREは以下の役割を持っています.

  • サービスの環境構築・運用
  • サービスの可用性・パフォーマンスの維持・向上
  • 障害対応
  • CI/CD整備
  • 開発環境の整備
  • パフォーマンスに懸念があるロジックのレビューや改善
  • セキュリティの担保
  • ログ収集・分析基盤の構築・運用
  • 中長期を見据えたアーキテクチャや基盤を先回りして検討や設計 

などがあります. ※1
これだけ見ると、インフラエンジニアと言われる職種に他の役割を足したように見えますよね.

プライベートでやってきたこと

プライベートではこれといって活動をしてはいません.
強いてやっていることは、

  • 読書
  • 情報収集

の2つです.

技術書や技術書に限らず、気になる本やいずれ必要になりそうな本を空き時間に消化しています.
これは持論なんですが、「本に答えを求めておらず、読んだ内容を元に自分なりに咀嚼し解釈、背景の想像・理解、現実への適用方法、代替方法の模索・検討、他の技術・概念との関係性を考えるために読む」というのを意識して本を読んでいます.

業務としてやってきたこと

参考にはなるかは分かりませんが、当たり障りのない範囲で業務としてやってきたことを書きます.

  • 組織内の開発環境整備
  • 新サービスの設計、構築、CI/CD整備
  • バックアップ設計、開発、構築
  • 監視基盤設計・見直し・実装
  • 不要なリソースの洗い出しや削除
  • コンテナ推進
  • Infrastructure as a Code推進
  • SRE Office Hoursの開催
  • 社内勉強会でのLT
  • 技術書の輪読

それぞれの業務について概要を書きたいですが、長くなるのでいくつかピックアップします.

コンテナが最適解だとは思いませんが、コンテナ技術を導入することで効率が良くなったり、楽ができると判断したため開発環境の整備やコンテナ推進を進めています.
The Twelve-Factor Appをベースにしたチェックリストを作りましたも推進に向けて、プライベートの時間で自分なりに解釈した結果を書いていました.
実際の業務ではこれを組織に落とし込んで、各サービスレベルで落とし込んで作業をしています.

某社のSREは役割が多岐に渡るため、組織が急激に成長するといつか破綻してしまうというリスクがありました.
そういったリスクに対して事前に対策を打つための1手段として、SREが持っている知識や経験則を共有、SREと一緒に悩む場として「SRE Office Hours」という活動を毎週金曜日に枠を設けて実施しています.
この活動はマイクロサービスについて何でも聞けるOffice Hoursに参加したよ! #メルカリな日々に影響を受けて企画し、今も開催し続けています. ※2
技術書の輪読もそういった活動の一環で、「仕組みはどうなっているのか」「弊社ではどうなのか」「これを改善していけるのではないか」といった疑問を持つきっかけだったり、議論をする場として活動しています.
社内勉強会のLT枠でも、SREが持っている知識を共有する場の1つとして利用させてもらっています.

最後に

技術的な話が皆無で申し訳ないですが、某社SREとして1年間やってきたことを書きました.
ぶっちゃけスキルの範囲が広いし、やることは尽きないし、障害対応もしなきゃないし大変な仕事ではあります.
裏を返せば「自分に足りない知識を身につけ、自分の知識を元に挑戦したい」を達成することができる仕事で面白いと個人的には思います.

興味があるかたは是非SREにチャレンジしてみてください.

次はfnaotoさんが6日目の記事を書くようですね. 楽しみです!

※1 全てをSREがやっているわけではなく他チームと協力してやっている業務もあります
※2 この活動の具体的な内容について気になる人は、12/11公開予定の某社アドベントカレンダーの記事を見てくれるとうれしいです

InkdropからBoostnoteに乗り換えた

1年半程度はInkdropでブログの下書き・メモ・議事録の下書きをしてきましたが、
Inkdropである必要がなくなってきたので一旦Inkdropを解約してBoostnoteに乗り換えました.

メモ書きツールに求めるモノ

khasegawa.hatenablog.com

以前書いた記事をベースにメモ書きツールに求めるモノがどう変化したのかをまとめてみます.

太字の項目が今回追加された求めるモノです.
欲張りですね.

複数のプラットフォーム間でデータを共有できること

マルチプラットフォームで動作することは大事ですが、データが共有されることも重要です.
同期速度が速いことが理想ですが、起動してネットワークに接続し、自動起動されるアプリケーションが起動し終わる頃に同期が終了していれば問題ありません.

キーバインドVimライクにできること

エンジニアとしてアルバイトをしている時から、開発するときにはVimもしくはNeovimを使っています.
そのため、メモツールでもできる限り同じ操作ができることが望ましいです.

何故、InkdropからBoostnoteへ変えたのか

まずは「何故、Inkdropをやめたのか」について書きます.
積極的な理由はなかったのですが、既に私が利用しているツールやサービスとOSSなツールで組み合わせて使える他の手段がないか?を模索していた結果、「一旦Inkdropを使うのをやめてみよう」と思うに至りました

次に「何故、Boostnoteを選んだのか」について書きます.
私のメモ書きツールに求めるモノを満たすのは、私が観測している範囲ではInkdropのみです.
では何故、Boostnoteにしたのか?という点が疑問になるはずですが、Boostnoteがどれだけ要求事項を満たしているかを書くにしてみましょう.

「複数のプラットフォーム間でデータを共有できること」以外は満たしていることになるため、何らかの形でデータを共有できれば全ての要求事項を満たすことができます.
Boostnoteでは、Cloud Syncing and Backupというページが公式から用意されており、DropboxGoogle Driveの共有フォルダを使うように書いてありました.
今回はそれに則ってGoogle Driveを利用して同期し、「複数のプラットフォーム間でデータを共有できること」を満たすようにしてみます.

Google Driveを使って同期する

Google Driveを手元のPCと同期させるには、バックアップと同期ドライブファイルストリームと2種類のやり方が存在します.
それぞれの違いについては、Googleドライブヘルプのバックアップと同期、ドライブ ファイル ストリームの違いを参照してください.

ここでは、今回の利用用途で関係ありそうな箇所を抜粋しました.

バックアップと同期 ドライブファイルストリーム
マイドライブ内のファイルを使用する
マイドライブ内の選択したフォルダのみを同期する
マイドライブの個別のファイルのみを同期する 不可
個人の Google アカウントで使用する 不可

これら2つの違いは、「個別ファイルの同期の可否」と「個人のGoogleアカウントでの利用可否」になりそうですね.
私の利用用途で考えると、Boostnote用のディレクトリのみを個別に同期したいですし、GSuiteを個人契約しているため、ドライブファイルストリームを使うのが良さそうです.

ドライブファイルストーリムを導入するを参考に導入し、Boostnote用のディレクトリ作成します.
後はそのディレクトリを同期すれば準備は完了です.

f:id:corrupt952:20210131110408j:plain

Boostnote with DFSの導入自体は非常に簡単でした.

Boostnoteに乗り換えて良かったこと・不満に感じていること

導入し実際に使ってみて以下の項目は良かったと感じました.

  • 1つのスニペットで複数のファイルを定義できる
  • Inkdropよりも軽快に感じる
  • GSuiteを契約していれば別途利用料金は発生しない

しかし、Boostnoteに乗り換えてみて良かったことばかりではありません.
以下の項目は導入してみて多少なりとも不満に感じている項目です.

最後に

久しぶりに自分のメモツールについて見直しました.
改めて振り返ると自分の求めているモノを言語化する必要性を感じます.

Boostnoteに乗り換えて良かったことが私が想定していたよりも少なかったですが、Inkdropの時と比べて不便というのは感じていません.
そのため、正直InkdropかBoostnoteかはたまた別のエディタなのかは、個人の求めるモノによって大きく変わりそうです.

また近いうちに変更しそうですが、今年度はBoostnoteでメモを書いてみようと思います.

The Twelve-Factor Appをベースにしたチェックリストを作りました

最近の仕事では、コンテナ化を推進しています.
あるサービスをコンテナ化するにあたって、参考にされるのはThe Twelve-Factor Appですが、原文でも邦訳でも各項目の解釈しづらいです.
そこでもう少し抽象度を下げたチェックリストを書きました.

Containerization checklist

作成したチェックリストは以下になりますが、Gistでも公開しているのでそちらを参照するのが良いと思います.

Codebase

  • [ ] Codebaseとアプリケーションは1対1であること
    • アプリケーション≠サービス
    • 複数のCodebaseから1つのアプリケーションが作られる場合はパイプラインが複雑になってしまうため
    • Configが異なる環境が複数あるのはアプリケーションが複数あるのではなく、デプロイが複数あると解釈する
  • [ ] 1つのCodebaseから複数の環境で実行可能な状態であること
    • ただし環境ごとに異なる設定についてはここでは触れない

Dependencies

システム側にインストールされるパッケージのことを、便宜上Site packagesと呼ぶ.
また、システム側にインストールされないGemなどのパッケージのことを、便宜上Vendoringと呼ぶ.

  • [ ] アプリケーションの実行に必要なSite packagesは、開発・テスト・ステージング・本番で利用される共通コンテナイメージでインストールしていること
    • libmysql-clientやimagemagickなどのパッケージのこと
  • [ ] アプリケーションから外部コマンドを実行しないこと
    • Rubyの場合、systemメソッドなどで外部コマンドを実行するのが該当する
  • [ ] アプリケーションの実行に必要なVendoringは、Gemfileなどのファイルに定義していること
  • [ ] アプリケーションの実行に必要なVendoringは、ステージング・本番向けのコンテナイメージにはインストールしていること

Config

Config

  • データベースなどのバックエンドサービスへの接続情報
  • AWSTwitterなどの外部サービスの認証情報
  • ホスト名や環境ごとに異なる値

のようなものを指す.

  • [ ] 環境によって異なる設定ファイルを定義しないこと
    • Frameworkなどが提供する設定ファイル(config/environments/test.rbなど)は対象外とする
  • [ ] 環境によって異なる設定値は環境変数で上書き可能な状態であること
  • [ ] 認証情報などのセンシティブな設定値はCodebaseに含めないこと
    • 「開発・テストでしか使わない情報」かつ「ステージング・本番で使う情報と異なる」場合はCodebaseに含めても良い

Backing services

Backing servicesは、アプリケーションがネットワーク越しに利用する全てのサービスやアプリケーションを指す.

  • [ ] 環境によって異なる接続先であっても、Codebaseを変更せず環境変数の変更のみで切替えられること

Build, Release, Run

  • [ ] デプロイメントパイプラインは、Build, Relase, Runの3つのステージに分離していること
    • [ ] Build ... アプリケーションが実行可能なコンテナイメージにビルドされること
    • [ ] Release ... コンテナイメージをRegistryにアップロードし、k8sのManifestやECSのTask Definitionが生成していること
    • [ ] Run ... Releaseで生成されたManifestなどを元にアプリケーションが実行されること
  • [ ] 全てのReleaseは一意のIDから特定できること
    • 連番やタイムスタンプなど一意に特定できれば良い

Processes

  • [ ] アプリケーションはステートレスであること
    • 例)セッション情報をオンメモリに保存しない
  • [ ] アプリケーションが何らかのステートを持つ場合は、ステートフルなBacking servicesに保存していること

Port binding

  • [ ] アプリケーションは単体でポートを公開し、サービスとして動作すること

Concurrency

  • [ ] 水平スケール可能なアプリケーションになっていること
    • Processesが守られていればこちらも守られているといえる

Disposability

  • [ ] SIGTERMシグナルが送られたプロセスはグレースフルに終了すること
    • [ ] Webプロセスの場合は、新規にリクエストを受け付けず、決められた時間内にレスポンスを返すこと
    • [ ] Workerプロセスの場合は、決められた時間内にジョブを完了させるか、ジョブのロックを解除し終了すること

Dev/prod parity

  • [ ] 開発・ステージング・本番で動作する環境はできる限り同一であること
    • できる限りはチーム内で合意を取り進める
  • [ ] アプリケーション開発者がステージング・本番環境へデプロイ可能であること

Logs

  • [ ] ログ収集の仕組みに乗せないログは、stdout,stderrに出力していること
  • [ ] ログ収集の仕組みに乗せるログは、チームで合意をとった方法で出力していること

Admin processes

Ruby on Railsならrails db:migrateAdmin processesに該当する.

  • [ ] 一度のみ実行されるようなAdmin processesはデプロイパイプラインに組み込まれていること
  • [ ] Admin processesで実行されるスクリプトやコードは、アプリケーションと同じコードベースで管理していること

さいごに

The Twelve-Factor Appをベースにチェックリストを作りましたが、本番環境で動作させるためにはまだまだ足りないです.
また今回は抽象度を少し下げましたが、各サービス固有のドメインについてのチェックリストまでには触れていません.
仕事で必要になる度にアウトプットをある程度まとめていくために

aws ssm start-sessionのラッパースクリプトを書きました

最近はsshではなくsession-manager-plguinを使い、Session Manager経由でインスタンス上で操作を行っています. ただsession-manager-pluginを使って接続する時はインスタンスIDを毎回指定するのが面倒だったので、
インスタンスIDの代わりにインスタンス名を選択すれば良くなるラッパースクリプトを作りました.

何故、Session Managerを使うの?

そもそも何故、Session Managerを使うの?と疑問に思う方もいるので
Session Managerとsshを比べた時のPros/Consを書いておきます.

Pros

  • (AWS上に)証跡を残すことができる
  • インスタンスへ接続するための鍵管理をしなくてよい
  • インスタンスへの接続制限をIAMポリシーで定義できる
  • ssh用のインバウンド通信を許可しなくてよい
  • どうしてもssh使いたい場合はSession Manager経由でsshも可能

Cons

  • sshを利用するコマンドが愚直には使えない(scp)
  • ブラウザ版を使っているとレスポンスが悪いときがある
  • 接続後にログインシェルを起動しないとコマンド履歴が残らない
  • インスタンス自身が外部と通信できるようにしなければならない(Public IPの割当やNat Gateway

と個人的には感じています.
そのため、「開発をする想定でなければ事足りる」という判断で、Session Managerを使っています.
IAMとは別に接続用の鍵を管理するのはできればやりたくないです. ※1

ssm-session

このように事前にインスタンスIDを控えておく必要があるため、接続することになった時にリズムが崩れてしまいます.
私は開発でもオペレーションでもリズムを重視しているので、これは看過できません.
というわけでこれのラッパースクリプトを書きました.

github.com

シンプルなラッパースクリプトなので難しいことはしていません.

  1. インスタンス一覧を取得
  2. 取得した情報からインスタンス名とインスタンスIDのみを切り出す
  3. 2で切り出した情報をfzfで1つ選択する
  4. 選択したインスタンスaws ssm start-sessionを行う

といった一連の操作をパイプで繋げるだけの簡単なスクリプトになっています.
使い方としては

ssm-session start

を実行すれば起動しているインスタンス一覧が出てくるのは、接続したいインスタンスを選択すれば後は接続してくれます.
大分お手軽になりました.

さいごに

今の所SSMの中でもSession Managerしか使っていないため、Documentを実行する時がきたら、名前やコマンドを見直すかもしれません.
また引数でインスタンスIDを受け取ったり、インスタンス名とインスタンスID以外の情報を出せるようにするかもしれません.

※1 EC2 Instance Connectでも十分ですが、ユースケースに応じて選択すればよいと思います.