GitHub で複数アカウントを使い分ける方法+おハマり話

GitHub Enterprise Cloud (GHEC) で Enterprise Managed Users (EMU) を利用している場合、同じ github.com で複数のアカウントを使い分ける必要があります。

検索すれば設定方法は見つかるのですが、何点かハマったのでその記録です。

環境は次のとおりです。

GitHub の世界

GitHubSSH でレポジトリにアクセスする場合は git@github.com:XXXXX/XXXXX.git と指定します。即ち、SSH のユーザー名は git で固定されます。GitLab や Bitbucket も同じですね。

では GitHub 内でどうやってアカウントを判別しているかというと、SSH の鍵で判別しています。(他のアカウントに登録している SSH 鍵を別の GitHub アカウントで登録しようとすると Key is already in use とエラーが出ます)

つまり、 GitHub複数アカウントを使い分けるには、別々の SSH 鍵を使い分ける 必要があります。

ssh の世界

SSH で同じ github.com へのアクセスで SSH 鍵を使い分けるには Host を変えて設定すればよいです。

Host github.com
  HostName github.com
  UpdateHostKeys yes
  IdentityFile ~/.ssh/id_ecdsa
  IdentitiesOnly  yes

Host oreno.github.com
  HostName github.com
  UpdateHostKeys yes
  IdentityFile ~/.ssh/oreno
  IdentitiesOnly  yes

次のようにすれば、GitHub のアカウント名が確認できます。

ssh -T git@github.com
# もしくは
ssh -T git@oreno.github.com

=> Hi hirose31! You've successfully authenticated, but GitHub does not provide shell access.

利用頻度が高い方を github.com に、低い方を別名にするとよいでしょう。

これで、

git clone git@oreno.github.com:omaeno/darekano.git

orenoGitHub アカウントで clone できます。

なお、パブリックな GitHub のレポジトリは、認証不要なので HTTPS でアクセスすることにしました。

git の世界

oreno の場合は git の設定をちょっと変えたいとき、例えばコミットの Author を変えたいときは、このようなファイルを用意しておいて、

# ~/.gitconfig.oreno
[user]
  email = oreno@example.com
  name  = Oreno Mono
# ~/.gitconfig.oreno-org
[user]
  email = oreno+soshiki@example.com
  name  = Oreno Soshiki

~/repos/ の中に ホスト名/organization名/レポジトリ名 で clone している場合、~/.gitconfig にこのように設定すればできます。

# ~/.gitconfig

# ~/repos/oreno.github.com/ の中にある、clone したレポジトリ全部に適用。
[includeIf "gitdir:~/repos/oreno.github.com/"]
  path = ~/.gitconfig.oreno

# 更に、organizaion 名が `-oreno` で終わる場合に上書き設定。
[includeIf "gitdir:~/repos/oreno.github.com/*-oreno/"]
  path = ~/.gitconfig.oreno-org

git-config(1) に拠ると、gitdir: では *** といったワイルドカードが使えるので活用するとよいでしょう。

ちなみに gh

orenogit clone した場合、.git/config に記載される remote.origin.url のホスト名は oreno.github.com になるので、GitHub CLIgh browse なんかが動かないんじゃないかな?と思ったのですが、gh やあと hubremote.origin.url は参照しておらず、GitHub API でレポジトリの情報を得て URL を組み立てているようで問題ありませんでした。

おハマり話

こんなスンナリ設定できたわけではなく、色々ハマったのでそのお話です。

複数の IdentityFile 指定

いくら設定を見直しても、ssh -T git@oreno.github.comoreno じゃない方の GitHub アカウント扱いになっちゃってたんですよね。

で、

ssh_config(5) に拠ると、

Since the first obtained value for each parameter is used,

SSH の設定は最初にマッチした設定値が採用されるとあります。

ただし、IdentityFile は例外的に、

It is possible to have multiple identity files specified in configuration files; all these identities will be tried in sequence. Multiple IdentityFile directives will add to the list of identities tried (this behaviour differs from that of other configuration directives).

複数指定可能で、その場合は試行する SSH 鍵のリストに追加されます。

さて、私の秘伝のタレ的な ~/.ssh/config では末尾にこういう設定を書いていました。

...

Host  *
  ...いろいろ...
  IdentityFile  ~/.ssh/id_ecdsa
  ...いろいろ...

(記憶が曖昧ですが、id_ecdsa を優先的に使ってほしくて指定したんじゃないかと思います)

【試行する SSH 鍵の順番】は、複数の鍵が ssh-agent に登録されている場合、IdentityFile の設定順序とは関係なく、 id_rsaid_ecdsaid_ecdsa_skid_ed25519id_ed25519_skこれら以外の名前の鍵 の順となるようです。

したがって、oreno.github.comSSH アクセスすると、orenoid_ecdsaSSH 鍵の試行リストとなり、最初に id_ecdsa を試して成功するので oreno で期待した GitHub アカウントにならなかった次第です!

  • 原則として IdentityFile は指定しない。
    • 指定が無ければ【試行する SSH 鍵の順番】の順に試行するのでそれに任せる。
  • 今回の github.com の設定のように、どうしても個別で指定したい場合だけ指定する。
    • IdentitiesOnly yes も指定する。

ように ~/.ssh/config を書き直しました。

ちなみに ssh-agent に複数の鍵が登録されている場合の施行順序は、 IdentitiesOnly の設定でも変化しこんな感じでした。

  • 単一の IdentityFile があり、IdentitiesOnly yes
    • → 単一の IdentityFile を試行する。
  • 複数の IdentityFile があり、IdentitiesOnly yes
    • → 複数の IdentityFile を【試行する SSH 鍵の順番】の順に試行する。
  • 単一の IdentityFile があり、IdentitiesOnly no
    • → 単一の IdentityFile を試行した後、ssh-agent に登録されている鍵を【試行する SSH 鍵の順番】の順に試行する。
  • 複数の IdentityFile があり、IdentitiesOnly no
    • → 複数の IdentityFile を【試行する SSH 鍵の順番】の順に試行した後、ssh-agent に登録されている鍵を【試行する SSH 鍵の順番】の順に試行する。

鍵の順番を確認するには、ちょっとデバッグログが多いですが、ssh -v git@github.com するとよいです。

ControlPath

ControlPath で指定できるトークンの %h は、実際に接続するリモートホスト名です。つまり、Host の値ではなく HostName の値になるのでどちらも github.com になります。

なので、最初に ssh -T git@oreno.github.com すると、そのセッションが再利用されるので、後発で ssh -T git@github.com しても orenoGitHub アカウントになります!

Host の設定を試行錯誤しているときにめちゃんこハマりました…

ControlPath では %nコマンドラインで指定された元のリモートホスト名)を使い、試行錯誤している最中は ssh -S none して ControlPath を無効化するとよいと思います。

forward 先の ssh-agent

他のホストに ssh ログインして、そこで forward された ssh-agent の鍵を利用して GitHub にアクセスする場合、他のホストには SSH 鍵のファイルは存在しない(よね?)ので、IdentityFile が効きません。

「他のホストに ssh ログインするときの鍵」と「GitHub にアクセスするときの鍵」が同じなら問題ないのですが、深遠な理由で異なる場合、どうするか。

「他のホストに ssh ログインするときの鍵」と「GitHub にアクセスするときの鍵」の鍵の種類を異なるものして、PubkeyAcceptedAlgorithms でその種類を強制するようにしました。

Host github.com
  HostName github.com
  UpdateHostKeys yes
  ### 存在しないので書かない。
  # IdentityFile ~/.ssh/id_ecdsa
  ### IdentityFile を指定していないので no 。
  IdentitiesOnly  no
  # ecdsa の鍵を使うように指定。
  PubkeyAcceptedAlgorithms ecdsa-sha2-nistp256

もっといい方法がありそうですが…

AWS と GCP と Azure のクライアントライブラリ(Python)のドキュメントのありか

毎回迷子になるのでまとめときます。

AWS

概要

の中に、高レベルの Resource低レベルの Client があります。

すべてのサービスで Resource が実装されているわけではないので、統一性を求めて個人的には Client を使っています。でも、Resource は Collection の all()ec2.instances.all())や filter() を呼ぶとイテレータが返ってきて、それを for で回せばよいのでいちいち paginator の処理を書かなくてよいのは便利ですね。

Resource

  • リファレンス
  • サンプルコード
    • Resource や Action のページにコード片が掲載されているので、それを参考にするとよいです。

Client

tips

たくさんのアカウントでなにかしたい

たくさんのアカウントでなにかしたいときに、それぞれのアカウントのクレデンシャルを用意するのはたいへんなので、Organizations を導入しているのが前提ですが、assume role を使うとよいです。

こんな感じのを用意して、

def assumed_session(
    *,
    management_session: boto3.session.Session,
    assumed_account_id: str,
    role_name='OrganizationAccountAccessRole',
) -> boto3.session.Session:

    res = management_session.client('sts').assume_role(
        RoleArn=f"arn:aws:iam::{assumed_account_id}:role/{role_name}",
        RoleSessionName=f"{assumed_account_id}@{role_name}"[:64],
    )

    return boto3.session.Session(
        aws_access_key_id=res['Credentials']['AccessKeyId'],
        aws_secret_access_key=res['Credentials']['SecretAccessKey'],
        aws_session_token=res['Credentials']['SessionToken'],
    )

管理アカウントのセッションとメンバーアカウントの ID を渡すと、メンバーアカウントのセッションが返るので、それを使って Resource や Client を作って処理します。

さらに、任意の関数を受けて concurrent.futures.ThreadPoolExecutor を使って並列実行する便利関数を用意しておくといろいろ捗ります。

GCP

概要

の 2 種類があります。

https://cloud.google.com/apis/docs/client-libraries-explained に依れば、

一部の Google Cloud APIs では、言語によっては Cloud クライアント ライブラリを利用できません。これらの API のいずれかを使用する際に希望する言語の Cloud クライアント ライブラリが存在しない場合は、以前のスタイルのクライアント ライブラリ(Google API クライアント ライブラリ)を引き続き使用できます。

とのことなので、使いたいサービスが高レベルの google-cloud-python で実装されていればそれを、そうでなければ低レベルの google-api-python-client を使うとよさそうです。

が、昔(今は改善されてるかも知れません)、google-cloud-python が意図した通りに動かなくてハマったことがあるので、自分は google-api-python-client を使うようにしています。

google-cloud-python

google-api-python-client

API

tips

update は丸ごと置き換えの場合が多いので注意しましょう

update メソッドはぜーんぶまるごと総入れ替えの場合が多いです。ドキュメントに注意書きがあるはずですが、見落として部分更新のつもりで一部のパラメータだけ添えて update すると、指定しなかったパラメータの値が消えちゃうことがあるので注意しましょう。

多くの API では部分更新の patch メソッドが提供されているのでそれを使うか、まず現在の値を得た後、一部の値を変更してすべて update メソッドに添えるかしましょう。

gcloud の credentials を拝借する

API アクセスに必要な credentials は色々な方法で用意できますが、予め gcloud auth login して生成された credentials を拝借することもできます。

#!/usr/bin/env python3

import json
import os
import sqlite3
import sys

import google.auth.transport.requests
import google.oauth2.credentials


def credentials_from_gcloud() -> google.oauth2.credentials.Credentials:
    """gcloud の credentials を拝借する"""

    con = sqlite3.connect(os.path.expanduser('~/.config/gcloud/credentials.db'))
    con.row_factory = sqlite3.Row
    rows = con.execute('''
    SELECT *
      FROM credentials
     WHERE account_id LIKE "%@gmail.com" -- 適当に変えて使ってください。
    ''').fetchall()

    assert len(rows) == 1

    cred_data = json.loads(rows[0]['value'])
    cred = google.oauth2.credentials.Credentials(
        'gcloud',
        client_id=cred_data['client_id'],
        client_secret=cred_data['client_secret'],
        token_uri=cred_data['token_uri'],
        refresh_token=cred_data['refresh_token'],
    )

    try:
        cred.refresh(google.auth.transport.requests.Request())

    except google.auth.exceptions.RefreshError as e:
        print('期限切れてるみたいだから gcloud auth login してねん。', file=sys.stderr)
        raise e

    return cred


credentials = credentials_from_gcloud()

プロジェクトの一覧を得る方法

Resource Manager APIprojects.list でプロジェクトの一覧を得ることができます。

が、Google Workspace(旧 GSuite)を利用していて組織リソースが存在する場合、何も考えずに projects.list するとおびただしい数のプロジェクトが返ってくるかもしれません。

これは、Apps Script プロジェクトが作成されると同時に GCP のプロジェクトも作成され、それが 組織/system-gsuite/apps-script/ のフォルダの中に配置されるためです。

なので、リソースの管理apps-script フォルダの ID(数字です)を調べて、そのフォルダ ID を親とするプロジェクトは除外するようにすればよいです。

import googleapiclient.discovery

projects: list[dict] = []

# 除外する apps-script フォルダの ID
apps_script_folder_id = '999999999999'

crm_client = googleapiclient.discovery.build(
    'cloudresourcemanager', 'v3',
    credentials=credentials,
)
projects_r = crm_client.projects()

request = projects_r.list(
    filter=f"NOT parent.id={apps_script_folder_id}"
)

while request is not None:
    response = request.execute()

    projects.extend(response['projects'])

    request = projects_r.list_next(request, response)

for project in projects:
    print(project['projectId'])

また、請求先アカウントにリンクされているプロジェクトの一覧(=非課金の野良プロジェクトは除外したい)なら、Cloud Billing APIbillingAccounts.projects.list を使うのもよいと思います。

gcloud がアクセスしている API を知りたい

--verbosity=debug を指定すると API のエンドポイントが表示されます。

例えば、gcloud alpha services quota list がアクセスしている API を知りたい時は、このように実行すると、

$ gcloud --verbosity=debug alpha services quota list --service='bigquery.googleapis.com' --consumer='projects/oreno-project'`

DEBUG: Running [gcloud.alpha.services.quota.list] with arguments: [--consumer: "projects/oreno-project", --service: "bigquery.googleapis.com", --verbosity: "debug"]
DEBUG: Starting new HTTPS connection (1): serviceusage.googleapis.com:443
DEBUG: https://serviceusage.googleapis.com:443 "GET /v1beta1/projects/oreno-project/services/bigquery.googleapis.com/consumerQuotaMetrics?alt=json HTTP/1.1" 200 None
...

と出力され、Service Usage APIv1beta1services.consumerQuotaMetrics にアクセスしていることがわかります。

Azure

概要

サービスごとにパッケージが分かれていてめちゃんこたくさんあります。

Python API ブラウザー で探すでもよし。

API

tips

サンプルコード

azure-sdk-for-pythonsdk/*/*/generated_samples/ にあるので参考にするとよいです。

クレデンシャル

az login でログインした状態なら azure.identity.DefaultAzureCredential() で得られる。

基本形

例えば Subscriptions だとこんな感じ。

# XxxClient は、API リファレンスの Management / azure.mgmt.subscription / Overview を見ると XxxClient の一覧がある。
subscription_client = azure.mgmt.subscription.SubscriptionClient(credential=DefaultAzureCredential())

# subscriptions とかは、API リファレンスの Management / azure.mgmt.subscription / XxxClient の Variables に一覧がある。
# get や list とかのメソッドは、API リファレンスの Management / azure.mgmt.subscription / operations / XxxOperations に一覧がある。
res = subscription_client.subscriptions.get(subscription_id='xxx')

# 結果(の要素)は as_dict() で dict に変換できるっぽ。
print(res.as_dict())

API バージョンの指定

結論から言うと、API バージョンの指定はしない。

サービスによっては複数のバージョンの実装があったりする。

例えば azure.mgmt.resource.ResourceManagementClient だとたくさんある

最新バージョンだけ使えばいいわけではなく、バージョンによって実装されているものが増減するサービスがある!

バージョンの指定は XxxClientapi_version で指定できるので、必要なサービスに応じてここで細かくバージョン指定…する必要はなくて、バージョン指定しなければ いい感じに最新のバージョンを使ってくれるみたい。少なくとも 2025-05 時点のパッケージでは。

使用している API バージョンの確認は logging.basicConfig(level=logging.DEBUG) するとリクエストした URL が表示されるんで、それで確認するのが手っ取り早い。

Graph API

AD グループとかは Graph API を使うみたい。

ぼくのかんがえたさいきょうの /etc/apt/sources.list

これ

# /etc/apt/sources.list
deb     mirror+file:/etc/apt/mirrors.txt jammy main restricted universe multiverse
deb-src mirror+file:/etc/apt/mirrors.txt jammy main restricted universe multiverse

deb     mirror+file:/etc/apt/mirrors.txt jammy-updates main restricted universe multiverse
deb-src mirror+file:/etc/apt/mirrors.txt jammy-updates main restricted universe multiverse

deb     http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
deb-src http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
# /etc/apt/mirrors.txt
# http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/  priority:1
# http://asia-northeast1.gce.archive.ubuntu.com/ubuntu/ priority:1
https://ftp.udx.icscoe.jp/Linux/ubuntu/ priority:2
https://linux.yz.yamagata-u.ac.jp/ubuntu/   priority:3
http://archive.ubuntu.com/ubuntu/

です。

これで、

  • ftp.udx.icscoe.jp にアクセスして成功したらそれで OK
  • 失敗したら linux.yz.yamagata-u.ac.jp にアクセスして成功したらそれで OK
  • 更に失敗したら最後の砦の http://archive.ubuntu.com/ubuntu/ にアクセスする

な動作になります。

EC2 や GCE な向きは、リージョンを適宜変更して(まれによくコケる)REGION.ec2.archive.ubuntu.comREGION.gce.archive.ubuntu.com/ubuntu/ のコメントを外して最初に試行するとよいでしょう。

/etc/apt/mirrors.txt について詳しく説明すると、

  • priority の値が小さい順に試行して、失敗したら次のを試行する
  • priority の値がないのは最後に試行する
  • priority の値が同じもの同士、もしくは priority の値がないもの同士はその中からランダムで選ばれ、失敗したらその中からまたランダムで選んで試行を繰り返す
  • URL と priority の間はタブで区切る! スペースではダメ!!
    • 今回の超絶おハマりポイントです!!
    • cat -T /etc/apt/mirrors.txt^I となっているか確認しましょう
  • この機能が使えるのは apt 1.6 以上で、bionic のが 1.6.x なので bionic より古いと使えないかもです

詳細は apt-transport-mirror(1) を参照してください。

公式の apt repos のミラーの一覧はここにあるので、お好みのを選んでみてください。

ときに、爆速の ICSCoE が以前は掲載されていた記憶なんですが、公式ミラーから外れちゃったんすかね?

おハマりその 2

do-release-upgrade するときは事前に元の source.list に戻しましょう。

do-release-upgrade の過程で source.list の中のコードネームが書き換わるのですが、少なくとも bionic → focal のときはmirror+file: の行が bionic のままでエラー終了しちゃいました。

Ruby の net-ssh で OpenSSH 8.8 以上のサーバーにアクセスできない件

Ubuntu 22.04 LTS (Jammy) の OpenSSH 8.9 な sshd に対して、OpenSSH 7.2 以降の ssh ではアクセスできるけど Rubynet-ssh ではアクセスできない件。

理由は以下の通り。

  • OpenSSH 8.8 で ssh-rsa 署名が無効化された
  • rsa 鍵は ssh-rsa 署名の他に、rsa-sha2-256rsa-sha2-512 でも署名可能
  • 実装によっては、ネゴシエーション時に利用可能とわかれば、rsa-sha2-256rsa-sha2-512 署名を使う
    • OpenSSH は 7.2 以降で対応している
    • Rubynet-ssh は現時点の最新リリース版 6.1.0 では未対応
  • 従って、
    • OpenSSH 7.2 以降の sshrsa-sha2-512 署名を使って接続可能
    • Rubynet-sshssh-rsa 署名を使うので接続失敗

詳しい説明は、ヌーラボさんの「OpenSSHがSHA-1を使用したRSA署名を廃止、BacklogのGitで発生した問題と解決にいたるまでの道のり」とそこからリンクされているサイトを参照されたし。

Rubynet-ssh で接続するには、

これからSwitchをお迎えする人へ

年末に向け、新しく Switch をお迎えする人も多いかと思います。そんな人へ、スムーズに使い始められるようにお勧めの準備事項をまとめてみました。

(以前、某所に投稿したやつの 2021 年版です)

SDカード

ダウンロードソフトの保存先に必要なので買っときましょう。本体内蔵のは有機 EL モデルが 64GB、無印と Lite が 32GB なんであっという間にいっぱいになります。

microSDXC の 128GB 以上ならいいんでないかと。

粗悪品を掴まされないように、信頼できる販路で信頼できるメーカーのを購入しましょう。(個人的には SanDiskTranscend

ちなみにソフトの容量はこんな感じ:

  • ブレワイ: 14.4GB
  • Xenoblade DE: 13.6GB
  • スプラ2: 6.1GB
  • あつ森: 6.6GB

ニンテンドーアカウント

Switch内の「ユーザー」に「ニンテンドーアカウント」を連携させるとポイント溜まったりニンテンドーeショップでソフト買えたりできます。

ので、「ニンテンドーアカウント」は予め作っておいた方がスムーズに始められるかと。

Switch が無くても PC or スマホでここからアカウント作れます。

ちな、Nintendo Switch Online には「ニンテンドーアカウント」は必須です。

子どもアカウント

0 歳から 17 歳までのアカウントは「子どもアカウント」として登録できて、「みまもり設定」で各種制限を加えることができます。

一度「子どもアカウント」にすると 13 歳以上になるまで「一般アカウント」にすることができませんが、ボイチャ使うゲームを子どもがやらない限りはまぁ「子どもアカウント」でいいんじゃないかと思います。

Nintendo Switch Online

Nintendo Switch Online は入るのをお勧めします。

オンラインプレイだけじゃなくて、セーブデータお預かり(セーブデータのバックアップ)もできるので。

300時間のハイラルの思い出が吹っ飛んだら立ち直れませんよね?

ただ、セーブデータお預かりに対応してないゲームもあるので気をつけてください…

あとは、昔のファミコンスーファミのゲームも遊べるようになります!

個人だと 2,400 円/年。ファミリープラン(最大 8 アカウント)だと 4,500 円/年です。

NINTENDO 64 好きなら、10 月下旬に追加予定の「追加パック」も要チェック。

Proコン

必要になったらでおk

ゲームはダウンロード版?パッケージ版?

ゲームカードを入れ替えるのが地味にめんどいのでダウンロード版をお勧めします。

が、パッケージ版のメリットもあるので状況次第で。

  • 飽きたら売り飛ばしたい
  • Switchを複数台もってて、両方でプレイしたい(連携してるニンテンドーアカウントが別の場合)
  • 中古で安く買いたい
  • ご賞味したい(ゲームカードは舐めると味がします。中古の場合は抵抗ありますが…)

液晶保護フィルム

持ち運び時にキズがついたらイヤなので自分は貼ってます。

キャリングケース

持ち運ぶときにはあった方がいいかもす。

カバンに放り込んで液晶に傷がついたりスティックがぐんにゃり逝ったりすると悲しいので、かさばらないソフトケース持ってます。

あと、ケースに入れても入れなくても、持ち運ぶときは電源オフにした方がいいす! カバンの中で、スティックに何かが触れるとスリープから覚めてしばらくしてまたスリープして〜を繰り返して、さてやるか!!ってときにバッテリーが尽きてると悲しいので。(経験者)

goのtviewを使う時は、

FAQ

にも書いてあるけど、 export LC_CTYPE="en_US.UTF-8" しましょう。

じゃないと、枠線がガビガビになったり行頭の1文字が書けたりしちゃいます。

もしくは、こんな感じで環境変数をセットして再実行するのでもいいかと。

func init() {
    // https://github.com/rivo/tview/wiki/FAQ#why-do-my-borders-look-weird
    if os.Getenv("LC_CTYPE") != "en_US.UTF-8" {
        os.Setenv("LC_CTYPE", "en_US.UTF-8")
        env := os.Environ()
        if err := syscall.Exec(os.Args[0], os.Args, env); err != nil {
            panic(err)
        }
    }
}

内部NLBによるパケットの書き換えのまとめ

そういえば、内部 NLB 構成ってどうやって同一セグメントのクライアントにパケットが返ってきてるんだろ? リアルサーバーからみたときに、パケットの送信元がクライアントのになる DSR だとすると返せるのはわかるんだけど、VIP (NLBのIPアドレス) 宛のパケットを受ける設定してなくても動いてるし、NAT (DNAT) 型だとすると(クライアントが同一セグメントにいるので)戻りのパケットが NAT した NLB を経由しないんでクライアントに破棄されるだろうし??? とふと疑問を持ったので調べてみた結果です。

調べたところ、リアルサーバーに届くパケットはこんな感じでした:

  • ターゲットをインスタンスIDで指定した場合
    • src MAC = NLBのMAC
    • src IP = クライアントのIP
    • dst MAC = リアルサーバーのMAC
    • dst IP = リアルサーバーのIP
  • ターゲットをIPアドレスで指定した場合
    • src MAC = NLBのMAC
    • src IP = NLBのIP
    • dst MAC = リアルサーバーのMAC
    • dst IP = リアルサーバーのIP

まとめると:

  • 内部 NLB は NAT型 の L4LB
  • dstはリアルサーバーのMACアドレスIPアドレスに書き換えられる(DNAT)
  • srcのMACアドレスはNLBのMACアドレスに書き換えられる
    • リアルサーバーからの戻りのパケットが必ずNLBを経由するようにこうなってると思われ
    • DNATのみだと、srcはクライアントのMAC、IPになるので、リアルサーバーはNLBを経由せずに直接クライアントに戻りのパケットを送信しちゃって、クライアントは送ったのと違うところからパケットが返って来ることになるので破棄しちゃう
  • ターゲットタイプで違うのは、srcのIPアドレスのみ
  • ターゲットをインスタンスIDで指定したときの注意点

な感じでした。なるほどー

良いお年を!