本文へ移動

1 - 認証ガイド

etcdの認証とロールベースのアクセス制御のガイド

1.1 - 認証

etcdクラスターの認証に関するガイド

認証に使用するauth、user、role:

export ETCDCTL_API=3
ENDPOINTS=localhost:2379

etcdctl --endpoints=${ENDPOINTS} role add root
etcdctl --endpoints=${ENDPOINTS} role get root

etcdctl --endpoints=${ENDPOINTS} user add root
etcdctl --endpoints=${ENDPOINTS} user grant-role root root
etcdctl --endpoints=${ENDPOINTS} user get root

etcdctl --endpoints=${ENDPOINTS} role add role0
etcdctl --endpoints=${ENDPOINTS} role grant-permission role0 readwrite foo
etcdctl --endpoints=${ENDPOINTS} user add user0
etcdctl --endpoints=${ENDPOINTS} user grant-role user0 role0

etcdctl --endpoints=${ENDPOINTS} auth enable
# now all client requests go through auth

etcdctl --endpoints=${ENDPOINTS} --user=user0:123 put foo bar
etcdctl --endpoints=${ENDPOINTS} get foo
# permission denied, user name is empty because the request does not issue an authentication request
etcdctl --endpoints=${ENDPOINTS} --user=user0:123 get foo
# user0 can read the key foo
etcdctl --endpoints=${ENDPOINTS} --user=user0:123 get foo1

注意:

これは、認証に関する情報を追加して更新する必要がある未完成のページです。上記のテキストはコード例にすぎません。

1.2 - ロールベースのアクセス制御

基本的な認証とロールベースのアクセス制御のガイド

概要

認証はetcd 2.1で追加されました。etcd v3 APIでは、新しいデータモデルに合わせて、認証機能のAPIとユーザーインターフェースを少し変更しています。このガイドは、etcd v3で基本的な認証とロールベースのアクセス制御を設定するためのものです。

特別なユーザーとロール

特別なユーザーrootと、特別なロールrootが1つずつあります。

ユーザーroot

etcdへの完全なアクセス権を持つrootユーザーは、認証を有効にする前に作成しなければなりません。rootユーザーは、ロールや通常のユーザーを管理するためのものです。rootユーザーにはrootロールが必要で、etcd内のあらゆるものを変更できます。

ロールroot

rootロールは、rootユーザーに限らず、任意のユーザーに付与できます。rootロールを持つユーザーには、全体への読み書きのアクセス権と、クラスターの認証設定を更新する権限があります。また、rootロールは、メンバー構成の変更、ストアのデフラグメンテーション、スナップショットの取得など、クラスターの一般的な保守権限も与えます。

ユーザーの操作

etcdctlのuserサブコマンドは、ユーザーアカウントに関するすべての操作を扱います。

次のコマンドでユーザーを一覧表示できます。

$ etcdctl user list

ユーザーは、次のように簡単に作成できます。

$ etcdctl user add myusername

新しいユーザーを作成すると、新しいパスワードの入力を求められます。--interactive=falseオプションを指定すると、標準入力からパスワードを渡せます。--new-user-passwordを使ってパスワードを指定することもできます。

次のように、パスワードで認証できないユーザーを作成することもできます。

$ etcdctl user add myusername --no-password

このようなユーザーは、TLSのCommon Nameによる認証 だけを使用できます。

注記

etcdは、--user username:による空のパスワードでの認証に対応していません。たとえば、etcdctl user add anonymous:''のように空のパスワードで作成したユーザーは、ユーザー名とパスワードを使うリクエストでは認証できません。etcdctl --user anonymous: get fooなどのリクエストは、user name is emptyで失敗します。

次のコマンドで、ユーザーにロールを付与したり、取り消したりできます。

$ etcdctl user grant-role myusername foo
$ etcdctl user revoke-role myusername bar

ユーザーの設定は、次のコマンドで確認できます。

$ etcdctl user get myusername

ユーザーのパスワードは、次のコマンドで変更できます。

$ etcdctl user passwd myusername

パスワードの変更時にも、新しいパスワードの入力を求められます。--interactive=falseオプションを指定すると、標準入力からパスワードを渡せます。

次のコマンドでアカウントを削除します。

$ etcdctl user delete myusername

ロールの操作

etcdctlのroleサブコマンドは、個々のユーザーに付与する各ロールのアクセス制御に関するすべての操作を扱います。

次のコマンドでロールを一覧表示します。

$ etcdctl role list

次のコマンドで新しいロールを作成します。

$ etcdctl role add myrolename

ロールにパスワードはありません。新しいアクセス権の集合を定義するだけです。

ロールには、単一のキーまたはキーの範囲へのアクセス権を付与します。

範囲は区間[start-key, end-key)として指定できます。start-keyは、アルファベットの辞書順でend-keyより小さくすることを推奨します。

次の例のように、読み取り、書き込み、または両方のアクセス権を付与できます。

# Give read access to a key /foo
$ etcdctl role grant-permission myrolename read /foo

# Give read access to keys with a prefix /foo/. The prefix is equal to the range [/foo/, /foo0)
$ etcdctl role grant-permission myrolename --prefix=true read /foo/

# Give write-only access to the key at /foo/bar
$ etcdctl role grant-permission myrolename write /foo/bar

# Give full access to keys in a range of [key1, key5)
$ etcdctl role grant-permission myrolename readwrite key1 key5

# Give full access to keys with a prefix /pub/
$ etcdctl role grant-permission myrolename --prefix=true readwrite /pub/

付与されている権限は、いつでもロールを参照して確認できます。

$ etcdctl role get myrolename

権限の取り消しも同様に行います。

$ etcdctl role revoke-permission myrolename /foo/bar

ロール全体の削除も同様です。

$ etcdctl role delete myrolename

認証の有効化

認証を有効にする最小限の手順は次のとおりです。管理者は、認証を有効にする前でも後でも、都合に合わせてユーザーとロールを設定できます。

rootユーザーを作成しておきます。

$ etcdctl user add root
Password of root:

認証を有効にします。

$ etcdctl auth enable

これで、etcdは認証が有効な状態で動作します。何らかの理由で無効にする場合は、逆の操作となる次のコマンドを使用します。

$ etcdctl --user root:rootpw auth disable

認証による保護範囲

etcdctl auth enableで認証を有効にすると、V3 gRPC APIの操作(get、put、delete、watchなど)が保護されます。

HTTPエンドポイント/metricsと/healthは別のハンドラーで動作し、V3 RBAC認証では保護されません。この設計により、キー・バリューデータを保護しつつ、PrometheusやロードバランサーはgRPC認証なしでメトリクスを収集できます。

これらの可観測性エンドポイントを保護するには、次の方法があります。

  • --cert-file、--key-file、--client-cert-authでmTLSを有効にする
  • または、--listen-metrics-urlsでメトリクスをプライベートなインターフェースにバインドする
  • または、ネットワークポリシーやファイアウォールルールでアクセスを制限する

etcdctlでの認証

etcdctlは、curlと似た認証用フラグをサポートします。

$ etcdctl --user user:password get foo

パスワードは、プロンプトから入力できます。

$ etcdctl --user user get foo

コマンドラインフラグ--passwordで指定することもできます。

$ etcdctl --user user --password password get foo

それ以外は、すべてのetcdctlコマンドの使い方は変わりません。ユーザーとロールは引き続き作成、変更できますが、rootロールを持つユーザーによる認証が必要です。

TLSのCommon Nameの利用

v3.2以降、etcdサーバーを--client-cert-auth=trueオプションで起動すると、クライアントのTLS証明書のCommon Name(CN)フィールドをetcdユーザーとして使用します。この場合、Common Nameがユーザーを認証するため、クライアントにパスワードは不要です。ただし、1. --client-cert-auth=trueを指定してクライアントがCNを提供し、かつ2. クライアントがユーザー名とパスワードも提供する場合は、ユーザー名とパスワードによる認証が優先されます。この機能はgRPC-proxyとgRPC-gatewayでは使えません。gRPC-proxyはクライアントからのTLSを終端するため、すべてのクライアントでプロキシーの証明書を共有するからです。gRPC-gatewayも、HTTPリクエストをgRPCリクエストに変換するために内部でTLS接続を使うので、同じ制限があります。このため、クライアントは自身のCNをサーバーに正しく提供できません。指定された証明書に空でないCNがあると、gRPC-proxyはエラーで停止します。gRPC-proxyが返すエラーは、クライアントの証明書のCNが空でないことを示します。

パスワード強度に関する注意

etcdctlとetcd APIは、ユーザーの作成時やパスワードの更新時に、特定のパスワード長を強制しません。こうした要件を適用する責任は管理者にあります。パスワード強度に伴うセキュリティーリスクを避けるには、TLSのCommon Nameによる認証 と、--no-passwordオプションで作成したユーザーを利用できます。

2 - 設定オプション

etcdの設定ファイル、フラグ、環境変数

etcdは、次の方法で設定できます。

警告

注意:設定方法を組み合わせた場合は、次の規則が適用されます。

  • コマンドラインフラグは環境変数より優先されます。
  • 設定ファイルを指定すると、すべてのコマンドラインフラグと環境変数は無視されます。

コマンドラインフラグ

以下では、フラグを--flag-name DEFAULT_VALUEの形式で示します。

注記

この取り込み済みリファレンスには、移行の背景として一部の旧v2オプションを残しています。etcd 3.7では、--enable-v2とv2プロキシーモードは利用できません。非推奨の--v2-deprecationフラグはユーザーの指定を無視し、デフォルト値を強制します。旧オプションを使う前に、インストールしたバージョンのetcd --helpを確認してください。

開発による変更が続いているため、以下のフラグ一覧は最新でない場合があります。利用可能な最新のフラグは、etcd --helpを実行するか、etcdのヘルプ を参照してください。

注記

注記:v3.7で新規追加、更新、非推奨となったフラグの詳細は、CHANGELOG-3.7.md を参照してください。

メンバー

--name 'default'
  Human-readable name for this member.
--data-dir '${name}.etcd'
  Path to the data directory.
--wal-dir ''
  Path to the dedicated wal directory.
--snapshot-count '10000'
  Number of committed transactions to trigger a snapshot to disk.
--heartbeat-interval '100'
  Time (in milliseconds) of a heartbeat interval.
--election-timeout '1000'
  Time (in milliseconds) for an election to timeout. See tuning documentation for details.
--initial-election-tick-advance 'true'
  Whether to fast-forward initial election ticks on boot for faster election.
--listen-peer-urls 'http://localhost:2380'
  List of URLs to listen on for peer traffic.
--listen-client-urls 'http://localhost:2379'
  List of URLs to listen on for client grpc traffic and http as long as --listen-client-http-urls is not specified.
--listen-client-http-urls ''
  List of URLs to listen on for http only client traffic. Enabling this flag removes http services from --listen-client-urls.
--max-snapshots '5'
  Maximum number of snapshot files to retain (0 is unlimited).
--max-wals '5'
  Maximum number of wal files to retain (0 is unlimited).
--memory-mlock
  Enable to enforce etcd pages (in particular bbolt) to stay in RAM.
--quota-backend-bytes '0'
  Raise alarms when backend size exceeds the given quota (0 defaults to low space quota).
--backend-bbolt-freelist-type 'map'
  BackendFreelistType specifies the type of freelist that boltdb backend uses(array and map are supported types).
--backend-batch-interval ''
  BackendBatchInterval is the maximum time before commit the backend transaction.
--backend-batch-limit '0'
  BackendBatchLimit is the maximum operations before commit the backend transaction.
--max-txn-ops '128'
  Maximum number of operations permitted in a transaction.
--max-request-bytes '1572864'
  Maximum client request size in bytes the server will accept.
--grpc-keepalive-min-time '5s'
  Minimum duration interval that a client should wait before pinging server.
--grpc-keepalive-interval '2h'
  Frequency duration of server-to-client ping to check if a connection is alive (0 to disable).
--grpc-keepalive-timeout '20s'
  Additional duration of wait before closing a non-responsive connection (0 to disable).
--socket-reuse-port 'false'
  Enable to set socket option SO_REUSEPORT on listeners allowing rebinding of a port already in use.
--socket-reuse-address 'false'
  Enable to set socket option SO_REUSEADDR on listeners allowing binding to an address in TIME_WAIT state.

クラスタリング

--initial-advertise-peer-urls 'http://localhost:2380'
  List of this member's peer URLs to advertise to the rest of the cluster.
--initial-cluster 'default=http://localhost:2380'
  Initial cluster configuration for bootstrapping.
--initial-cluster-state 'new'
  Initial cluster state ('new' or 'existing').
--initial-cluster-token 'etcd-cluster'
  Initial cluster token for the etcd cluster during bootstrap.
  Specifying this can protect you from unintended cross-cluster interaction when running multiple clusters.
--advertise-client-urls 'http://localhost:2379'
  List of this member's client URLs to advertise to the public.
  The client URLs advertised should be accessible to machines that talk to etcd cluster. etcd client libraries parse these URLs to connect to the cluster.
--discovery ''
  Discovery URL used to bootstrap the cluster.
--discovery-fallback 'proxy'
  Expected behavior ('exit' or 'proxy') when discovery services fails.
  "proxy" supports v2 API only.
--discovery-proxy ''
  HTTP proxy to use for traffic to discovery service.
--discovery-srv ''
  DNS srv domain used to bootstrap the cluster.
--discovery-srv-name ''
  Suffix to the dns srv name queried when bootstrapping.
--strict-reconfig-check 'true'
  Reject reconfiguration requests that would cause quorum loss.
--pre-vote 'true'
  Enable the raft Pre-Vote algorithm to prevent disruption when a node that has been partitioned away rejoins the cluster.
--auto-compaction-retention '0'
  Auto compaction retention length. 0 means disable auto compaction.
--auto-compaction-mode 'periodic'
  Interpret 'auto-compaction-retention' one of: periodic|revision. 'periodic' for duration based retention, defaulting to hours if no time unit is provided (e.g. '5m'). 'revision' for revision number based retention.
--enable-v2 'false'
  Accept etcd V2 client requests. Deprecated and to be decommissioned in v3.6.
--v2-deprecation 'not-yet'
  Phase of v2store deprecation. Allows to opt-in for higher compatibility mode.
  Supported values:
    'not-yet'                // Issues a warning if v2store have meaningful content (default in v3.5)
    'write-only'             // Custom v2 state is not allowed (default in v3.6 and v3.7)
    'write-only-skip-check'  // Custom v2 state is not supported and, if present, will be ignored (available in v3.5.32+, v3.6.13+, and v3.7.0+). Use this option at your own risk.
    'write-only-drop-data'   // Custom v2 state will get DELETED ! (planned default in v3.8)
    'gone'                   // v2store is not maintained any longer.

セキュリティー

--cert-file ''
  Path to the client server TLS cert file.
--key-file ''
  Path to the client server TLS key file.
--client-cert-auth 'false'
  Enable client cert authentication.
  It's recommended to enable client cert authentication to prevent attacks from unauthenticated clients (e.g. CVE-2023-44487), especially when running etcd as a public service.
--client-crl-file ''
  Path to the client certificate revocation list file.
--client-cert-allowed-hostname ''
  Comma-separated list of SAN hostnames for client cert authentication.
--trusted-ca-file ''
  Path to the client server TLS trusted CA cert file.
  Note setting this parameter will also automatically enable client cert authentication no matter what value is set for `--client-cert-auth`.
--auto-tls 'false'
  Client TLS using generated certificates.
--peer-cert-file ''
  Path to the peer server TLS cert file.
--peer-key-file ''
  Path to the peer server TLS key file.
--peer-client-cert-auth 'false'
  Enable peer client cert authentication.
  It's recommended to enable peer client cert authentication to prevent attacks from unauthenticated forged peers (e.g. CVE-2023-44487).
--peer-trusted-ca-file ''
  Path to the peer server TLS trusted CA file.
--peer-cert-allowed-cn ''
  Comma-separated list of allowed CNs for inter-peer TLS authentication.
--peer-cert-allowed-hostname ''
  Comma-separated list of allowed SAN hostnames for inter-peer TLS authentication.
--peer-auto-tls 'false'
  Peer TLS using self-generated certificates if --peer-key-file and --peer-cert-file are not provided.
--self-signed-cert-validity '1'
  The validity period of the client and peer certificates that are automatically generated by etcd when you specify ClientAutoTLS and PeerAutoTLS, the unit is year, and the default is 1.
--peer-crl-file ''
  Path to the peer certificate revocation list file.
--cipher-suites ''
  Comma-separated list of supported TLS cipher suites between client/server and peers (empty will be auto-populated by Go).
--cors '*'
  Comma-separated whitelist of origins for CORS, or cross-origin resource sharing, (empty or * means allow all).
--host-whitelist '*'
  Acceptable hostnames from HTTP client requests, if server is not secure (empty or * means allow all).
--tls-min-version 'TLS1.2'
  Minimum TLS version supported by etcd.
--tls-max-version ''
  Maximum TLS version supported by etcd (empty will be auto-populated by Go).

認証

--auth-token 'simple'
  Specify a v3 authentication token type and its options ('simple' or 'jwt').
--bcrypt-cost 10
  Specify the cost / strength of the bcrypt algorithm for hashing auth passwords. Valid values are between 4 and 31.
--auth-token-ttl 300
  Time (in seconds) of the auth-token-ttl.

プロファイリングと監視

--enable-pprof 'false'
  Enable runtime profiling data via HTTP server. Address is at client URL + "/debug/pprof/"
--metrics 'basic'
  Set level of detail for exported metrics, specify 'extensive' to include server side grpc histogram metrics.
--listen-metrics-urls ''
  List of URLs to listen on for the metrics and health endpoints.

ログ

--logger 'zap'
  Currently only supports 'zap' for structured logging.
--log-outputs 'default'
  Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd, or list of comma separated output targets.
--log-level 'info'
  Configures log level. Only supports debug, info, warn, error, panic, or fatal.
--log-format 'json'
  Configures log format. Only supports json, console.
--enable-log-rotation 'false'
  Enable log rotation of a single log-outputs file target.
--log-rotation-config-json '{"maxsize": 100, "maxage": 0, "maxbackups": 0, "localtime": false, "compress": false}'
  Configures log rotation if enabled with a JSON logger config. MaxSize(MB), MaxAge(days,0=no limit), MaxBackups(0=no limit), LocalTime(use computers local time), Compress(gzip)".
--warning-unary-request-duration '300ms'
  Set time duration after which a warning is logged if a unary request takes more than this duration.
注記

注記:v3.7では、複数の--experimental-*フラグが安定版に昇格、または改名されています。 非推奨のフラグは、以下に記載する対応する安定版のフラグに必ず置き換えてください。

分散トレーシング

--enable-distributed-tracing 'false'
  Enable distributed tracing.
--distributed-tracing-address 'localhost:4317'
  Distributed tracing collector address.
--distributed-tracing-service-name 'etcd'
  Distributed tracing service name, must be the same across all etcd instances.
--distributed-tracing-instance-id ''
  Distributed tracing instance ID, must be unique for each etcd instance.
--distributed-tracing-sampling-rate '0'
  Number of samples to collect per million spans for distributed tracing.

v2プロキシー

警告

v2プロキシーはv3.7より前に削除されました。以下のフラグは、過去の移行の参考資料としてのみ残しており、etcd v3.7サーバーに指定してはいけません。

--proxy 'off'
  Proxy mode setting ('off', 'readonly' or 'on').
--proxy-failure-wait 5000
  Time (in milliseconds) an endpoint will be held in a failed state.
--proxy-refresh-interval 30000
  Time (in milliseconds) of the endpoints refresh interval.
--proxy-dial-timeout 1000
  Time (in milliseconds) for a dial to timeout.
--proxy-write-timeout 5000
  Time (in milliseconds) for a write to timeout.
--proxy-read-timeout 0
  Time (in milliseconds) for a read to timeout.

機能

--corrupt-check-time '0s'
  Duration of time between cluster corruption check passes.
--compact-hash-check-time '1m'
  Duration of time between leader checks followers compaction hashes.
--compaction-batch-limit 1000
  CompactionBatchLimit sets the maximum revisions deleted in each compaction batch.
--peer-skip-client-san-verification 'false'
  Skip verification of SAN field in client certificate for peer connections.
--watch-progress-notify-interval '10m'
  Duration of periodical watch progress notification.
--warning-apply-duration '100ms'
  Warning is generated if requests take more than this duration.
--bootstrap-defrag-threshold-megabytes
  Enable the defrag during etcd server bootstrap on condition that it will free at least the provided threshold of disk space. Needs to be set to non-zero value to take effect.
--max-learners '1'
  Set the max number of learner members allowed in the cluster membership.
--compaction-sleep-interval
  Sets the sleep interval between each compaction batch.
--downgrade-check-time
  Duration of time between two downgrade status checks.
--snapshot-catchup-entries
  Number of entries for a slow follower to catch up after compacting the raft storage entries.

フィーチャーゲート

--feature-gates=AllAlpha=true|false
  Enables or disables all alpha features. Default is false.
--feature-gates=AllBeta=true|false
  Enables or disables all beta features. Default is false.
--feature-gates=CompactHashCheck=true
  Enables leader to periodically check follower compaction hashes.
  Replaces: --experimental-compact-hash-check-enabled
--feature-gates=InitialCorruptCheck=true
  Enables corruption check before serving client/peer traffic.
  Replaces: --experimental-initial-corrupt-check
--feature-gates=LeaseCheckpoint=true
  ExperimentalEnableLeaseCheckpoint enables primary lessor to persist lease remainingTTL to prevent indefinite auto-renewal of long lived leases.
  Replaces: --experimental-enable-lease-checkpoint
--feature-gates=LeaseCheckpointPersist=true
  Enable persisting remainingTTL to prevent indefinite auto-renewal of long lived leases. Always enabled in v3.6. Should be used to ensure smooth upgrade from v3.5 clusters with this feature enabled.
  Replaces: --experimental-enable-lease-checkpoint-persist
--feature-gates=SetMemberLocalAddr=true
  Allows setting a member’s local address.
--feature-gates=StopGRPCServiceOnDefrag=true
  Enable etcd gRPC service to stop serving client requests on defragmentation.
  Replaces: --experimental-stop-grpc-service-on-defrag
--feature-gates=TxnModeWriteWithSharedBuffer=true
  Enable the write transaction to use a shared buffer in its readonly check operations.
  Replaces: --experimental-txn-mode-write-with-shared-buffer

安全性を保証しない機能

警告

警告:安全性を保証しない機能を使用すると、コンセンサスプロトコルの保証が損なわれる可能性があります。

--force-new-cluster 'false'
  Force to create a new one-member cluster.
--unsafe-no-fsync 'false'
  Disables fsync, unsafe, will cause data loss.

設定ファイル

etcdの設定ファイルは、コマンドラインフラグ名をキー、そのフラグの値を値とするYAMLマップです。 このファイルを使うには、--config-fileフラグまたはETCD_CONFIG_FILE環境変数の値としてファイルパスを指定します。

例は、etcd.conf.ymlのサンプル を参照してください。

注記

--grpc-keepalive-min-time、--grpc-keepalive-interval、 --grpc-keepalive-timeout、--backend-batch-interval、--corrupt-check-time、 --compact-hash-check-time、--compaction-sleep-interval、 --watch-progress-notify-interval、--warning-apply-duration、 --warning-unary-request-duration、--downgrade-check-timeなどの期間フィールドは、 コマンドラインフラグでは人が読みやすい文字列(10m、5sなど)を受け付けますが、 設定ファイルではナノ秒を表す整数値しか受け付けません。これは、 time.Durationが単なる整数としてアンマーシャルされる、Go標準ライブラリの既知の制限 です。

たとえば、設定ファイルで監視の進捗通知間隔を10分にするには、次のようにします。

# Correct: 10 minutes in nanoseconds
watch-progress-notify-interval: 600000000000

# Incorrect: will produce an unmarshal error
watch-progress-notify-interval: '10m'

3 - 通信のセキュリティーモデル

通信中のデータの保護

etcdは、クライアントとサーバーの通信、およびピア間(サーバー間/クラスター内)の通信の両方で、自動TLSとクライアント証明書による認証に対応しています。etcdは、データベースを使い始める際の負担を減らすため、RBACによる認証 も通信層の認証機能も、デフォルトでは有効にしません。また、このデフォルトは2013年のプロジェクト発足以来のもので、変更すると互換性を損ないます。セキュリティー機能を有効にしていないetcdクラスターは、あらゆるクライアントにデータを公開してしまう可能性があります。

まず、CA証明書と、1メンバー用の署名済み鍵ペアを用意します。クラスターのメンバーごとに新しい鍵ペアを作成し、署名することを推奨します。

cfssl ツールを使うと、証明書を簡単に生成できます。このツールを使った例をこちら に示しています。また、自己署名鍵ペアの生成ガイド も参照できます。

開発による変更が続いているため、以下のフラグ一覧は最新でない場合があります。利用可能な最新のフラグは、etcd --helpを実行するか、etcdのヘルプ を参照してください。

基本設定

etcdには、コマンドラインフラグまたは環境変数で指定できる、証明書関連の設定オプションがいくつかあります。

クライアントとサーバーの通信:

--cert-file=<path>:etcdへのSSL/TLS接続に使う証明書。このオプションを設定すると、advertise-client-urlsでHTTPSスキームを使えます。

--key-file=<path>:証明書の鍵。暗号化されていてはなりません。

--client-cert-auth:指定すると、etcdはすべての受信HTTPSリクエストについて、信頼するCAが署名したクライアント証明書を確認します。有効なクライアント証明書を提示しないリクエストは失敗します。認証 が有効な場合、証明書はCommon Nameフィールドに指定されたユーザー名の認証情報を提供します。

--trusted-ca-file=<path>:信頼する認証局。

--auto-tls:クライアントとのTLS接続に、自動生成した自己署名証明書を使用します。

ピア間(サーバー間/クラスター内)の通信:

ピア用のオプションは、クライアントとサーバーの通信のオプションと同様に機能します。

--peer-cert-file=<path>:ピア間のSSL/TLS接続に使う証明書。ピアアドレスでのリッスンと、ほかのピアへのリクエスト送信の両方で使用します。

--peer-key-file=<path>:証明書の鍵。暗号化されていてはなりません。

--peer-client-cert-auth:指定すると、etcdはクラスターから受信するすべてのピアリクエストについて、指定のCAが署名した有効なクライアント証明書を確認します。

--peer-trusted-ca-file=<path>:信頼する認証局。

--peer-auto-tls:ピア間のTLS接続に、自動生成した自己署名証明書を使用します。

クライアントとサーバーの通信、またはピア間の通信に証明書を指定した場合、鍵も設定しなければなりません。これらの設定オプションはすべて、ETCD_TRUSTED_CA_FILE、ETCD_PEER_TRUSTED_CA_FILEなどの環境変数でも指定できます。

共通オプション:

--cipher-suites:サーバーとクライアント、およびピア間でサポートするTLS暗号スイートのカンマ区切りの一覧(空の場合はGoが自動設定します)。

--tls-min-version=<version>:etcdがサポートするTLSの最小バージョンを設定します。

--tls-max-version=<version>:etcdがサポートするTLSの最大バージョンを設定します。未設定の場合、Goがサポートする最大バージョンを使用します。

TLS証明書のkeyUsageとextendedKeyUsage

etcdの通信を保護するX.509証明書を生成するときは、その役割に応じた適切なkeyUsageとextendedKeyUsageフィールドを含めることを推奨します。etcdは証明書の検証にGoのcrypto/tlsとcrypto/x509ライブラリを使用し、これらのライブラリがTLSハンドシェイク中に用途を検証します。

次の表に、一般的な証明書の役割に対する推奨用途を示します。

証明書の役割 keyUsage extendedKeyUsage
サーバー(クライアントとサーバーの通信) digitalSignature, keyEncipherment serverAuth
クライアント digitalSignature, keyEncipherment clientAuth
ピア(サーバー間の通信) digitalSignature, keyEncipherment serverAuth, clientAuth

注意事項:

  • --peer-client-cert-authが有効な場合、ピア証明書はetcdメンバー間の相互TLSに使うため、serverAuthとclientAuthの両方が必要です。
  • --client-cert-authで使うクライアント証明書には、clientAuthを含めることを推奨します。

例1:HTTPSによるクライアントとサーバー間の通信の保護

CA証明書(ca.crt)と署名済み鍵ペア(server.crt、server.key)を用意します。

単純なHTTPSによる通信の保護を提供するよう、etcdを順に設定します。

$ etcd --name infra0 --data-dir infra0 \
  --cert-file=/path/to/server.crt --key-file=/path/to/server.key \
  --advertise-client-urls=https://127.0.0.1:2379 --listen-client-urls=https://127.0.0.1:2379

これで正常に起動するはずです。HTTPSでetcdにアクセスして設定をテストできます。

$ curl --cacert /path/to/ca.crt https://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -v

このコマンドで、ハンドシェイクの成功を確認できるはずです。独自の認証局による自己署名証明書を使用するため、--cacertオプションでCAをcurlに渡さなければなりません。CA証明書をシステムの信頼済み証明書ディレクトリ(通常は/etc/pki/tls/certsまたは/etc/ssl/certs)に追加する方法もあります。

OSX 10.9以降のユーザー:OSX 10.9以降のcurl 7.30.0は、コマンドラインから渡された証明書を認識しません。 代わりに、ダミーのca.crtをキーチェーンに直接インポートするか、curlに-kフラグを付けてエラーを無視してください。 -kなしでテストするには、open ./tests/fixtures/ca/ca.crtを実行し、表示される指示に従います。 テスト後は、この証明書を削除してください。 回避策があれば、お知らせください。

例2:HTTPSクライアント証明書によるクライアントとサーバー間の認証

ここまでは、etcdクライアントがサーバーの身元を検証し、通信を保護できるようにしました。クライアント証明書を使って、etcdへの不正アクセスを防ぐこともできます。

クライアントはサーバーに証明書を提示し、サーバーはそれが指定されたCAによる署名かどうかを確認して、リクエストを処理するか判断します。

最初の例のファイルに加えて、同じ認証局が署名したクライアント用鍵ペア(client.crt、client.key)が必要です。

$ etcd --name infra0 --data-dir infra0 \
  --client-cert-auth --trusted-ca-file=/path/to/ca.crt --cert-file=/path/to/server.crt --key-file=/path/to/server.key \
  --advertise-client-urls https://127.0.0.1:2379 --listen-client-urls https://127.0.0.1:2379

このサーバーに対し、先ほどと同じリクエストを試します。

$ curl --cacert /path/to/ca.crt https://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -v

サーバーはリクエストを拒否するはずです。

...
routines:SSL3_READ_BYTES:sslv3 alert bad certificate
...

成功させるには、CAが署名したクライアント証明書をサーバーに提示する必要があります。

$ curl --cacert /path/to/ca.crt --cert /path/to/client.crt --key /path/to/client.key \
  -L https://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -v

出力には、次の内容が含まれるはずです。

...
SSLv3, TLS handshake, CERT verify (15):
...
TLS handshake, Finished (20)

サーバーからのレスポンスも含まれます。

{
    "action": "set",
    "node": {
        "createdIndex": 12,
        "key": "/foo",
        "modifiedIndex": 12,
        "value": "bar"
    }
}

弱いTLS暗号スイート を拒否するには、暗号スイートを指定します。

無効な暗号スイートを含むClient Helloを送ると、TLSハンドシェイクは失敗します。

例:

$ etcd \
  --cert-file ./server.crt \
  --key-file ./server.key \
  --trusted-ca-file ./ca.crt \
  --cipher-suites TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

この場合、クライアントのリクエストは、サーバーに指定された暗号スイートのいずれかを指定しなければなりません。

# valid cipher suite
$ curl \
  --cacert /path/to/ca.crt \
  --cert /path/to/client.crt \
  --key /path/to/client.key \
  -L [CLIENT-URL]/metrics \
  --ciphers ECDHE-RSA-AES128-GCM-SHA256

# request succeeds
etcd_server_version{server_version="3.2.22"} 1
...
# invalid cipher suite
$ curl \
  --cacert /path/to/ca.crt \
  --cert /path/to/client.crt \
  --key /path/to/client.key \
  -L [CLIENT-URL]/metrics \
  --ciphers ECDHE-RSA-DES-CBC3-SHA

# request fails with
(35) error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure

例3:クラスター内の通信の保護とクライアント証明書

etcdは、ピア間通信、つまりクラスター内のetcdメンバー間の通信にも、上記と同じモデルをサポートします。

ca.crtと、このCAが署名した各メンバーの鍵ペア(member1.crtとmember1.key、member2.crtとmember2.key)を持つ2つのメンバーがあるとします。次のようにetcdを起動します。

DISCOVERY_URL=... # from https://discovery.etcd.io/new

# member1
$ etcd --name infra1 --data-dir infra1 \
  --peer-client-cert-auth --peer-trusted-ca-file=/path/to/ca.crt --peer-cert-file=/path/to/member1.crt --peer-key-file=/path/to/member1.key \
  --initial-advertise-peer-urls=https://10.0.1.10:2380 --listen-peer-urls=https://10.0.1.10:2380 \
  --discovery ${DISCOVERY_URL}

# member2
$ etcd --name infra2 --data-dir infra2 \
  --peer-client-cert-auth --peer-trusted-ca-file=/path/to/ca.crt --peer-cert-file=/path/to/member2.crt --peer-key-file=/path/to/member2.key \
  --initial-advertise-peer-urls=https://10.0.1.11:2380 --listen-peer-urls=https://10.0.1.11:2380 \
  --discovery ${DISCOVERY_URL}

etcdメンバーはクラスターを構成し、クラスター内のメンバー間通信はすべて暗号化され、クライアント証明書で認証されます。etcdの出力には、接続先のアドレスがHTTPSを使用していることが示されます。

例4:自動生成した自己署名証明書による通信の保護

警告

ClientAutoTLSとPeerAutoTLSを指定すると、etcdが自動生成するクライアント証明書とピア証明書の有効期間は1年だけです。–self-signed-cert-validityフラグで、証明書の有効期間を年単位で設定できます。

通信の暗号化は必要でも認証は不要な場合、etcdは自動生成した自己署名証明書でメッセージを暗号化できます。etcdの外で証明書や鍵を管理する必要がないため、配置が簡単になります。 --auto-tlsと--peer-auto-tlsフラグで、クライアント接続とピア接続に自己署名証明書を使うよう設定します。

DISCOVERY_URL=... # from https://discovery.etcd.io/new

# member1
$ etcd --name infra1 --data-dir infra1 \
  --auto-tls --peer-auto-tls \
  --initial-advertise-peer-urls=https://10.0.1.10:2380 --listen-peer-urls=https://10.0.1.10:2380 \
  --discovery ${DISCOVERY_URL}

# member2
$ etcd --name infra2 --data-dir infra2 \
  --auto-tls --peer-auto-tls \
  --initial-advertise-peer-urls=https://10.0.1.11:2380 --listen-peer-urls=https://10.0.1.11:2380 \
  --discovery ${DISCOVERY_URL}

自己署名証明書は身元を認証しないため、curlはエラーを返します。

curl: (60) SSL certificate problem: Invalid certificate chain

証明書チェーンの検証を無効にするには、-kフラグを付けてcurlを実行します。

$ curl -k https://127.0.0.1:2379/v2/keys/foo -Xput -d value=bar -v

DNS SRVに関する注意

v3.1.0以降(v3.2.9を除く)、SRVディスカバリーによるブートストラップは、--discovery-srvフラグのルートドメイン名でServerNameを認証します。証明書のSubject Alternative Name(SAN)フィールドに一致するルートドメイン名を要求することで、証明書を使う中間者攻撃を防ぎます。たとえば、etcd --discovery-srv=etcd.localでは、提供された証明書のSubject Alternative Name(SAN)フィールドにルートドメインetcd.localが含まれる場合にのみ、ピアやクライアントを認証します。

etcdプロキシーに関する注意

注記

このセクションでは、削除済みの旧v2プロキシーを説明します。etcd 3.7では、v3 gRPCプロキシー を使用してください。

etcdプロキシーは、クライアントとの接続が保護されている場合、そのTLSを終端します。etcdメンバーとの通信には、--peer-key-fileと--peer-cert-fileで指定したプロキシー自身の鍵と証明書を使います。

プロキシーは、各メンバーの--advertise-client-urlsと--advertise-peer-urlsの両方を使って通信します。クライアントリクエストはメンバーの通知するクライアントURLに転送し、初期クラスター構成はメンバーの通知するピアURLを通じて同期します。

etcdメンバーでクライアント認証が有効な場合、管理者は、プロキシーの--peer-cert-fileで指定したピア証明書がその認証に有効であることを確認しなければなりません。ピア認証も有効な場合は、プロキシーのピア証明書がピア認証にも有効でなければなりません。

TLS認証に関する注意

v3.2.0 以降、クライアント接続ごとにTLS証明書を再読み込みします 。etcdサーバーを停止せずに期限切れ証明書を交換する際に便利で、古い証明書を新しいものに上書きするだけで対応できます。接続ごとの証明書の更新に大きなオーバーヘッドは生じないはずですが、将来はキャッシュ層で改善することもできます。テスト例はこちら を参照してください。

v3.2.0 以降、サーバーはIPのSANが正しくないピア証明書を拒否します 。たとえば、ピア証明書のSubject Alternative Name(SAN)フィールドにIPアドレスがある場合、リモートIPアドレスがそのいずれかと一致するときだけ認証します。これは、許可されていないエンドポイントがクラスターに参加することを防ぐためです。たとえば、ピアBのCSR(cfsslを使用)が次のとおりだとします。

{
  "CN": "etcd peer",
  "hosts": [
    "*.example.default.svc",
    "*.example.default.svc.cluster.local",
    "10.138.0.27"
  ],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "CA",
      "ST": "San Francisco"
    }
  ]
}

一方、ピアBの実際のIPアドレスは10.138.0.27ではなく10.138.0.2です。Bがクラスターに参加しようとすると、ピアAはx509: certificate is valid for 10.138.0.27, not 10.138.0.2エラーで拒否します。BのリモートIPアドレスがSubject Alternative Name(SAN)フィールドのアドレスと一致しないためです。

v3.2.0 以降、サーバーはSANの検証時にTLSのDNSNamesを名前解決します 。たとえば、ピア証明書のSubject Alternative Name(SAN)フィールドにDNS名だけがあり、IPアドレスがない場合、そのDNS名を正引き(dig b.com)したIPがリモートIPアドレスに一致するときだけ、サーバーはピアを認証します。たとえば、ピアBのCSR(cfsslを使用)が次のとおりだとします。

{
  "CN": "etcd peer",
  "hosts": [
    "b.com"
  ],

ピアBのリモートIPアドレスは10.138.0.2です。Bがクラスターに参加しようとすると、ピアAは接続してきたホストb.comを名前解決し、IPアドレスの一覧を取得します(dig b.comなど)。一覧にIPアドレス10.138.0.2がなければ、tls: 10.138.0.2 does not match any of DNSNames ["b.com"]エラーでBを拒否します。

v3.2.2 以降、IPが一致すれば、サーバーはDNSエントリーを確認せずに接続を受け入れます 。たとえば、ピア証明書のSubject Alternative Name(SAN)フィールドにIPアドレスとDNS名があり、リモートIPアドレスがそのいずれかに一致する場合、サーバーはDNS名を追加で確認せずに接続を受け入れます。たとえば、ピアBのCSR(cfsslを使用)が次のとおりだとします。

{
  "CN": "etcd peer",
  "hosts": [
    "invalid.domain",
    "10.138.0.2"
  ],

ピアBのリモートIPアドレスは10.138.0.2で、invalid.domainは無効なホストです。Bがクラスターに参加しようとすると、Subject Alternative Name(SAN)フィールドに一致する有効なIPアドレスがあるため、ピアAはBの認証に成功します。詳しくは、issue#8206 を参照してください。

v3.2.5 以降、サーバーはワイルドカードDNSのSANの逆引きに対応します 。たとえば、ピア証明書のSubject Alternative Name(SAN)フィールドにDNS名だけがあり、IPアドレスがない場合、サーバーはまずリモートIPアドレスを逆引きし、そのアドレスに対応する名前の一覧を取得します(nslookup IPADDRなど)。その名前のいずれかがピア証明書のDNS名と完全一致またはワイルドカード一致すれば、接続を受け入れます。一致しなければ、ピア証明書の各DNSエントリーを正引きし(エントリーが*.example.default.svcならexample.default.svcを検索)、名前解決したアドレスがピアのリモートIPアドレスと一致するときだけ受け入れます。たとえば、ピアBのCSR(cfsslを使用)が次のとおりだとします。

{
  "CN": "etcd peer",
  "hosts": [
    "*.example.default.svc",
    "*.example.default.svc.cluster.local"
  ],

ピアBのリモートIPアドレスは10.138.0.2です。Bがクラスターに参加しようとすると、ピアAはIPアドレス10.138.0.2を逆引きしてホスト名の一覧を取得し、Bの証明書のSubject Alternative Name(SAN)フィールドのDNS名と、完全一致またはワイルドカード一致するかを確認します。逆引きでも正引きでも一致しなければ、"tls: "10.138.0.2" does not match any of DNSNames ["*.example.default.svc","*.example.default.svc.cluster.local"]というエラーを返します。詳しくは、issue#8268 を参照してください。

v3.3.0 では、etcd --peer-cert-allowed-cn フラグを追加し、ピア間接続のCN(Common Name)による認証 に対応しました。KubernetesのTLSブートストラップでは、etcdメンバーやほかのシステムコンポーネント(APIサーバー、kubeletなど)用に証明書を動的に生成します。コンポーネントごとに別のCAを管理するとetcdクラスターへのアクセスを厳しく制御できますが、多くの場合手間がかかります。–peer-cert-allowed-cnを指定すると、CAを共有していても、Common Nameが一致するノードだけが参加できます。照合は、証明書のCommon Name(CN)フィールドとの完全な文字列比較で、ワイルドカードや前方一致には対応しません。–peer-cert-allowed-hostnameや–client-cert-allowed-hostnameによるホスト名のフィルタリングは、Goのx509.Certificate.VerifyHostname()を使い、完全なホスト名とワイルドカード(*.example.comなど)の両方に対応します。たとえば、3ノードクラスターの各メンバーを、次のCSR(cfsslを使用)で設定します。

{
  "CN": "etcd.local",
  "hosts": [
    "m1.etcd.local",
    "127.0.0.1",
    "localhost"
  ],
{
  "CN": "etcd.local",
  "hosts": [
    "m2.etcd.local",
    "127.0.0.1",
    "localhost"
  ],
{
  "CN": "etcd.local",
  "hosts": [
    "m3.etcd.local",
    "127.0.0.1",
    "localhost"
  ],

--peer-cert-allowed-cn etcd.localを指定すると、Common Nameが一致するピアだけを認証します。CSRのCNや--peer-cert-allowed-cnが異なるノードは拒否します。

$ etcd --peer-cert-allowed-cn m1.etcd.local

I | embed: rejected connection from "127.0.0.1:48044" (error "CommonName authentication failed", ServerName "m1.etcd.local")
I | embed: rejected connection from "127.0.0.1:55702" (error "remote error: tls: bad certificate", ServerName "m3.etcd.local")

各プロセスを次のように起動します。

etcd --peer-cert-allowed-cn etcd.local

I | pkg/netutil: resolving m3.etcd.local:32380 to 127.0.0.1:32380
I | pkg/netutil: resolving m2.etcd.local:22380 to 127.0.0.1:22380
I | pkg/netutil: resolving m1.etcd.local:2380 to 127.0.0.1:2380
I | etcdserver: published {Name:m3 ClientURLs:[https://m3.etcd.local:32379]} to cluster 9db03f09b20de32b
I | embed: ready to serve client requests
I | etcdserver: published {Name:m1 ClientURLs:[https://m1.etcd.local:2379]} to cluster 9db03f09b20de32b
I | embed: ready to serve client requests
I | etcdserver: published {Name:m2 ClientURLs:[https://m2.etcd.local:22379]} to cluster 9db03f09b20de32b
I | embed: ready to serve client requests
I | embed: serving client requests on 127.0.0.1:32379
I | embed: serving client requests on 127.0.0.1:22379
I | embed: serving client requests on 127.0.0.1:2379

v3.2.19 とv3.3.4 では、証明書のSANフィールドにIPアドレスだけがあり、ドメイン名がない場合 のTLS再読み込みを修正しています。たとえば、あるメンバーを次のCSR(cfsslを使用)で設定します。

{
  "CN": "etcd.local",
  "hosts": [
    "127.0.0.1"
  ],

Goでは、サーバーの(*tls.Config).Certificatesフィールドが空の場合、またはクライアントからの有効なSNIで(*tls.ClientHelloInfo).ServerNameが空でない場合に限り、TLS再読み込みのために(*tls.Config).GetCertificateを呼び出します。以前のetcdは、最初のクライアントTLSハンドシェイク時に常に(*tls.Config).Certificatesを設定し、空でない状態にしていました。そのため、TLS検証に合格し、(*tls.Config).GetCertificateによるTLSファイルの再読み込みを発生させるには、クライアントが一致するSNIを必ず送ることを想定していました。

しかし、SANフィールドにドメイン名がなくIPアドレスしかない証明書 では、ServerNameが空の*tls.ClientHelloInfoでリクエストするため、最初のTLSハンドシェイクで再読み込みが発生しませんでした。これは、期限切れ証明書をオンラインで置き換える際に問題となります。

現在は、最初のTLSクライアントハンドシェイク時に(*tls.Config).Certificatesを空で作成し、まず(*tls.Config).GetCertificateを呼び出します。その後、新しいTLS接続ごとに残りの証明書を設定するため、クライアントのSNIが空の場合(証明書がIPだけを含む場合など)でも動作します。

ホスト許可リストに関する注意

etcd --host-whitelistフラグは、HTTPクライアントリクエストで許可するホスト名を指定します。クライアントオリジンポリシーは、保護されていないetcdサーバーへの「DNSリバインディング」 攻撃を防ぎます。任意のWebサイトが、許可されたDNS名を作成し、そのDNSを"localhost"(または任意のアドレス)に向けるだけで、"localhost"でリッスンするetcdサーバーのすべてのHTTPエンドポイントにアクセスできてしまい、DNSリバインディング攻撃に対して脆弱になるためです。詳しくは、CVE-2018-5702 を参照してください。

クライアントオリジンポリシーは、次のように機能します。

  1. HTTPSによる保護されたクライアント接続の場合、すべてのホスト名を許可します。
  2. 保護されていないクライアント接続で、"HostWhitelist"が空でない場合、Hostフィールドが許可リストにあるHTTPリクエストだけを許可します。

制御を厳格にするため、認証が有効かどうかにかかわらず、クライアントオリジンポリシーを適用します。

デフォルトでは、etcd --host-whitelistとembed.Config.HostWhitelistは*に設定され、すべてのホスト名を許可します。許可リストが空の場合も、すべてのホスト名を許可します。ホスト名を指定する場合、ループバックアドレスは自動追加されません。ループバックインターフェースを許可するには、許可リストに手動で追加してください("localhost"、"127.0.0.1"など)。

よくある質問

TLSクライアント認証でSSLv3 alert handshake failureが発生する場合

golangのcrypto/tlsパッケージは、証明書の公開鍵を使う前に鍵の用途を確認します。 証明書の公開鍵をクライアント認証に使うには、証明書の公開鍵を作成する際に、Extended Key UsageへclientAuthを追加する必要があります。

手順は次のとおりです。

openssl.cnfに、次のセクションを追加します。

[ ssl_client ]
...
  extendedKeyUsage = clientAuth
...

証明書を作成するときは、-extensionsフラグで必ず参照してください。

$ openssl ca -config openssl.cnf -policy policy_anything -extensions ssl_client -out certs/machine.crt -infiles machine.csr

ピア証明書認証で"certificate is valid for 127.0.0.1, not $MY_IP"が返る場合

メンバーのパブリックIPアドレスをSubject Nameに指定して、証明書に署名してください。たとえば、etcd-caツールにはnew-certコマンドの--ip=オプションがあります。

証明書はSubject NameにメンバーのFQDNを指定して署名し、Subject Alternative Names(略してIP SAN)でIPアドレスを追加する必要があります。etcd-caツールにはnew-certコマンドの--domain=オプションがあり、opensslでも同じこと ができます。

etcdはディスク上のデータを暗号化するか

いいえ。etcdは、ディスクに保存するキー・バリューデータを暗号化しません。etcdに保存するデータを暗号化する必要がある場合は、次の方法があります。

  • クライアントアプリケーションでデータを暗号化、復号する
  • dm-crypt など、基盤となるストレージシステムの保存データ暗号化機能を使う

etcdは特定のディレクトリを新規作成するとき、権限のないアクセスをできる限り防ぐため、ファイルの権限を700に設定します。ただし、ユーザーがすでに独自の設定でディレクトリを作成している場合、etcdは既存のディレクトリを使用し、権限が700と異なれば警告メッセージを記録します。

4 - クラスター構成ガイド

etcdクラスターのブートストラップ:静的構成、etcdディスカバリー、DNSディスカバリー

概要

etcdクラスターを静的に起動するには、各メンバーがクラスター内のほかのメンバーを把握している必要があります。ただし、メンバーのIPアドレスを事前に把握できない場合もあります。その場合は、ディスカバリーサービスを利用してクラスターをブートストラップできます。

etcdクラスターの起動後、メンバーの追加や削除は実行時再構成 で行います。その設計をより深く理解するには、実行時構成の設計ドキュメント を参照することを推奨します。

このガイドでは、etcdクラスターをブートストラップする次の方法を説明します。

いずれの方法でも、次の3台のマシンでetcdクラスターを作成します。

名前 アドレス ホスト名
infra0 10.0.1.10 infra0.example.com
infra1 10.0.1.11 infra1.example.com
infra2 10.0.1.12 infra2.example.com

静的構成

起動前にクラスターメンバー、そのアドレス、クラスターサイズがわかっているため、initial-clusterフラグを設定してオフラインでブートストラップを構成できます。各マシンに、次の環境変数またはコマンドラインを指定します。

ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380"
ETCD_INITIAL_CLUSTER_STATE=new
--initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
--initial-cluster-state new

initial-clusterに指定するURLは、通知するピアURLです。各ノードのinitial-advertise-peer-urlsの値と一致させることを推奨します。

テストのために同じ設定で複数のクラスターを起動する場合や、単一のクラスターを作成、破棄する場合は、各クラスターに一意のinitial-cluster-tokenを指定することを強く推奨します。これにより、ほかの設定が完全に同じでも、etcdは各クラスターに一意のクラスターIDとメンバーIDを生成できます。クラスター間の相互作用を防ぎ、それによる破損からetcdを保護できます。

etcdは、クライアントのトラフィックを受け付けるため、listen-client-urls でリッスンします。etcdメンバーは、advertise-client-urls に指定されたURLを、ほかのメンバー、プロキシー、クライアントに通知します。対象のクライアントからadvertise-client-urlsに到達できることを確認してください。リモートクライアントからetcdにアクセスする必要があるのに、advertise-client-urlsをlocalhostに設定したり、デフォルトのままにしたりするのは、よくある誤りです。

各マシンで、次のフラグを指定してetcdを起動します。

$ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 \
  --listen-peer-urls http://10.0.1.10:2380 \
  --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.10:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  --initial-cluster-state new
$ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 \
  --listen-peer-urls http://10.0.1.11:2380 \
  --listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.11:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  --initial-cluster-state new
$ etcd --name infra2 --initial-advertise-peer-urls http://10.0.1.12:2380 \
  --listen-peer-urls http://10.0.1.12:2380 \
  --listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.12:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  --initial-cluster-state new

--initial-clusterで始まるコマンドラインパラメーターは、etcdの2回目以降の実行では無視されます。初回のブートストラップ後は、環境変数やコマンドラインフラグを削除してかまいません。後から構成を変更する場合(メンバーの追加や削除など)は、実行時構成 ガイドを参照してください。

TLS

etcdは、TLSプロトコルによる暗号化通信に対応します。TLSチャネルは、ピア間のクラスター内部通信とクライアントのトラフィックの両方を暗号化できます。このセクションでは、ピアとクライアントにTLSを使うクラスターの設定例を示します。etcdのTLS対応の詳細は、セキュリティーガイド を参照してください。

自己署名証明書

自己署名証明書を使うクラスターは、通信を暗号化するとともに接続を認証します。そのようなクラスターを起動するには、各メンバーが、共通のクラスターCA証明書(ca.crt)で署名された一意の鍵ペア(member.crt、member.key)を持ち、ピア接続とクライアント接続の両方に使用することを推奨します。証明書は、etcdのTLS設定例 に従って生成できます。

各マシンで、次のフラグを指定してetcdを起動します。

$ etcd --name infra0 --initial-advertise-peer-urls https://10.0.1.10:2380 \
  --listen-peer-urls https://10.0.1.10:2380 \
  --listen-client-urls https://10.0.1.10:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.10:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 \
  --initial-cluster-state new \
  --client-cert-auth --trusted-ca-file=/path/to/ca-client.crt \
  --cert-file=/path/to/infra0-client.crt --key-file=/path/to/infra0-client.key \
  --peer-client-cert-auth --peer-trusted-ca-file=ca-peer.crt \
  --peer-cert-file=/path/to/infra0-peer.crt --peer-key-file=/path/to/infra0-peer.key
$ etcd --name infra1 --initial-advertise-peer-urls https://10.0.1.11:2380 \
  --listen-peer-urls https://10.0.1.11:2380 \
  --listen-client-urls https://10.0.1.11:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.11:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 \
  --initial-cluster-state new \
  --client-cert-auth --trusted-ca-file=/path/to/ca-client.crt \
  --cert-file=/path/to/infra1-client.crt --key-file=/path/to/infra1-client.key \
  --peer-client-cert-auth --peer-trusted-ca-file=ca-peer.crt \
  --peer-cert-file=/path/to/infra1-peer.crt --peer-key-file=/path/to/infra1-peer.key
$ etcd --name infra2 --initial-advertise-peer-urls https://10.0.1.12:2380 \
  --listen-peer-urls https://10.0.1.12:2380 \
  --listen-client-urls https://10.0.1.12:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.12:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 \
  --initial-cluster-state new \
  --client-cert-auth --trusted-ca-file=/path/to/ca-client.crt \
  --cert-file=/path/to/infra2-client.crt --key-file=/path/to/infra2-client.key \
  --peer-client-cert-auth --peer-trusted-ca-file=ca-peer.crt \
  --peer-cert-file=/path/to/infra2-peer.crt --peer-key-file=/path/to/infra2-peer.key

証明書の自動生成

暗号化通信が必要でも接続の認証は不要な場合、etcdに鍵を自動生成させることができます。初期化時に、各メンバーは通知するIPアドレスとホスト名に基づいて、自身の鍵を作成します。

各マシンで、次のフラグを指定してetcdを起動します。

$ etcd --name infra0 --initial-advertise-peer-urls https://10.0.1.10:2380 \
  --listen-peer-urls https://10.0.1.10:2380 \
  --listen-client-urls https://10.0.1.10:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.10:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 \
  --initial-cluster-state new \
  --auto-tls \
  --peer-auto-tls
$ etcd --name infra1 --initial-advertise-peer-urls https://10.0.1.11:2380 \
  --listen-peer-urls https://10.0.1.11:2380 \
  --listen-client-urls https://10.0.1.11:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.11:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 \
  --initial-cluster-state new \
  --auto-tls \
  --peer-auto-tls
$ etcd --name infra2 --initial-advertise-peer-urls https://10.0.1.12:2380 \
  --listen-peer-urls https://10.0.1.12:2380 \
  --listen-client-urls https://10.0.1.12:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.12:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 \
  --initial-cluster-state new \
  --auto-tls \
  --peer-auto-tls

エラーの例

次の例では、新しいホストをノード一覧に含めていません。新しいクラスターの場合は、初期クラスターメンバーの一覧に、このノードを追加しなければなりません。

$ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 \
  --listen-peer-urls https://10.0.1.11:2380 \
  --listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.11:2379 \
  --initial-cluster infra0=http://10.0.1.10:2380 \
  --initial-cluster-state new
etcd: infra1 not listed in the initial cluster config
exit 1

この例では、ノードinfra0を、クラスター一覧のアドレス(10.0.1.10:2380)と異なるアドレス(127.0.0.1:2380)に割り当てようとしています。このノードが複数のアドレスでリッスンする場合は、すべてのアドレスを"initial-cluster"設定に反映しなければなりません。

$ etcd --name infra0 --initial-advertise-peer-urls http://127.0.0.1:2380 \
  --listen-peer-urls http://10.0.1.10:2380 \
  --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.10:2379 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  --initial-cluster-state=new
etcd: error setting up initial cluster: infra0 has different advertised URLs in the cluster and advertised peer URLs list
exit 1

異なる設定引数で構成されたピアがこのクラスターに参加しようとすると、etcdはクラスターIDの不一致を報告して終了します。

$ etcd --name infra3 --initial-advertise-peer-urls http://10.0.1.13:2380 \
  --listen-peer-urls http://10.0.1.13:2380 \
  --listen-client-urls http://10.0.1.13:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.13:2379 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra3=http://10.0.1.13:2380 \
  --initial-cluster-state=new
etcd: conflicting cluster ID to the target cluster (c6ab534d07e8fcc4 != bc25ea2a74fb18b0). Exiting.
exit 1

ディスカバリー

クラスターピアのIPアドレスを事前に把握できない場合があります。クラウドプロバイダーの利用時や、ネットワークがDHCPを使う場合にはよくあります。その場合は、静的構成を指定する代わりに、既存のetcdクラスターを使って新しいクラスターをブートストラップします。この処理を「ディスカバリー」と呼びます。

ディスカバリーには、次の2つの方法を使用できます。

  • etcdディスカバリーサービス
  • DNS SRVレコード

etcdディスカバリー

注記

このセクションのディスカバリーサービスの例は、etcd 3.7で削除された旧v2プロトコルを説明しています。新しいv3.7クラスターには、v3ディスカバリー 、またはこのガイドの静的構成やDNS SRVによるブートストラップ方法を使用してください。

ディスカバリーサービスプロトコルの設計をより深く理解するには、そのドキュメント を参照することを推奨します。

ディスカバリーURLの有効期間

ディスカバリーURLは、etcdクラスターを一意に識別します。既存のURLを再利用せず、各etcdインスタンスで新しいURLを共有して、新しいクラスターをブートストラップします。

また、ディスカバリーURLはクラスターの初回ブートストラップにのみ使用することを推奨します。稼働開始後にメンバー構成を変更するには、実行時再構成 ガイドを参照してください。

独自のetcdディスカバリーサービス

ディスカバリーでは、既存のクラスターを使って新しいクラスターをブートストラップします。非公開のetcdクラスターを使う場合は、次のようにURLを作成します。

$ curl -X PUT https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=3

このURLにsizeキーを設定することで、想定するクラスターサイズが3のディスカバリーURLを作成します。

この場合に使用するURLはhttps://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83で、etcdメンバーは起動時にhttps://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83ディレクトリで登録を行います。

各メンバーのnameフラグには、異なる名前を指定しなければなりません。Hostnameやmachine-idが適しています。同じ名前を指定すると、重複によってディスカバリーが失敗します。

各メンバーで、対応するフラグを指定してetcdを起動します。

$ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 \
  --listen-peer-urls http://10.0.1.10:2380 \
  --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.10:2379 \
  --discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
$ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 \
  --listen-peer-urls http://10.0.1.11:2380 \
  --listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.11:2379 \
  --discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
$ etcd --name infra2 --initial-advertise-peer-urls http://10.0.1.12:2380 \
  --listen-peer-urls http://10.0.1.12:2380 \
  --listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.12:2379 \
  --discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83

各メンバーは独自のetcdディスカバリーサービスに登録し、すべてのマシンの登録が完了するとクラスターを開始します。

公開etcdディスカバリーサービス

利用できる既存のクラスターがなければ、discovery.etcd.ioの公開ディスカバリーサービスを使います。“new"エンドポイントで専用のディスカバリーURLを作成するには、次のコマンドを使用します。

$ curl https://discovery.etcd.io/new?size=3
https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

これにより、初期サイズが3メンバーのクラスターを作成します。サイズを指定しない場合は、デフォルトの3を使用します。

ETCD_DISCOVERY=https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
--discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

各メンバーのnameフラグには、異なる名前を指定しなければなりません。同じ名前を指定すると、重複によってディスカバリーが失敗します。Hostnameやmachine-idが適しています。

各メンバーで、対応するフラグを指定してetcdを起動します。

$ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 \
  --listen-peer-urls http://10.0.1.10:2380 \
  --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.10:2379 \
  --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
$ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 \
  --listen-peer-urls http://10.0.1.11:2380 \
  --listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.11:2379 \
  --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
$ etcd --name infra2 --initial-advertise-peer-urls http://10.0.1.12:2380 \
  --listen-peer-urls http://10.0.1.12:2380 \
  --listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.12:2379 \
  --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

各メンバーはディスカバリーサービスに登録し、すべてのメンバーの登録が完了するとクラスターを開始します。

環境変数ETCD_DISCOVERY_PROXYを使うと、etcdはHTTPプロキシーを経由してディスカバリーサービスに接続します。

エラーと警告の例

ディスカバリーサーバーのエラー
$ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 \
  --listen-peer-urls http://10.0.1.10:2380 \
  --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.10:2379 \
  --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
etcd: error: the cluster doesn’t have a size configuration value in https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de/_config
exit 1
警告

これは、このマシンではディスカバリーURLを無視するという、無害な警告です。

$ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 \
  --listen-peer-urls http://10.0.1.10:2380 \
  --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.10:2379 \
  --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
etcdserver: discovery token ignored since a cluster has already been initialized. Valid log found at /var/lib/etcd

DNSディスカバリー

DNSのSRVレコード をディスカバリーに使用できます。 --discovery-srvフラグで、ディスカバリー用SRVレコードを検索するDNSドメイン名を設定できます。 --discovery-srv example.comを指定すると、次の順にDNS SRVレコードを検索します。

  • _etcd-server-ssl._tcp.example.com
  • _etcd-server._tcp.example.com

_etcd-server-ssl._tcp.example.comが見つかると、etcdはTLSでのブートストラップを試みます。

クライアントがetcdクラスターを発見できるよう、次のDNS SRVレコードをこの順に検索します。

  • _etcd-client._tcp.example.com
  • _etcd-client-ssl._tcp.example.com

_etcd-client-ssl._tcp.example.comが見つかると、クライアントはSSL/TLSでetcdクラスターとの通信を試みます。

etcdでTLSを使う場合、SSL証明書のDNS SANにホスト名とともにディスカバリーSRVレコード(example.comなど)を含めなければなりません。含めないと、次のようなログメッセージを出力してクラスター構成に失敗します。

[...] rejected connection from "10.0.1.11:53162" (error "remote error: tls: bad certificate", ServerName "example.com")

etcdで独自の認証局を使わずにTLSを使う場合、ディスカバリードメイン(example.comなど)はSRVレコードのドメイン(infra1.example.comなど)と一致しなければなりません。これは、別のドメインを指す偽のSRVレコードを使う攻撃を軽減するためです。そのドメインは、PKI上は有効な証明書を持っていても、未知の第三者に管理されている可能性があります。

-discovery-srv-nameフラグを使うと、ディスカバリーで問い合わせるSRV名に接尾辞を追加できます。 同じドメイン内の複数のetcdクラスターを区別するために使います。 たとえば、discovery-srv=example.comと-discovery-srv-name=fooを設定すると、次のDNS SRVクエリーを実行します。

  • _etcd-server-ssl-foo._tcp.example.com
  • _etcd-server-foo._tcp.example.com

DNS SRVレコードの作成

$ dig +noall +answer SRV _etcd-server._tcp.example.com
_etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra0.example.com.
_etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra1.example.com.
_etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra2.example.com.
$ dig +noall +answer SRV _etcd-client._tcp.example.com
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra0.example.com.
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra1.example.com.
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra2.example.com.
$ dig +noall +answer infra0.example.com infra1.example.com infra2.example.com
infra0.example.com.  300  IN  A  10.0.1.10
infra1.example.com.  300  IN  A  10.0.1.11
infra2.example.com.  300  IN  A  10.0.1.12

DNSを使ったetcdクラスターのブートストラップ

etcdのクラスターメンバーは、ドメイン名またはIPアドレスを通知できます。ブートストラップ処理はDNS Aレコードを解決します。 3.2以降、--listen-peer-urlsと--listen-client-urlsは、ネットワークインターフェースのバインドにドメイン名を指定すると拒否します(3.1では警告を出力します)。

--initial-advertise-peer-urlsの名前解決後のアドレスは、SRVターゲットを名前解決したアドレスのいずれかと一致しなければなりません。etcdメンバーは、このアドレスを読み取り、SRVレコードが定義するクラスターに属するかどうかを判断します。

$ etcd --name infra0 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://infra0.example.com:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://infra0.example.com:2379 \
--listen-client-urls http://0.0.0.0:2379 \
--listen-peer-urls http://0.0.0.0:2380
$ etcd --name infra1 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://infra1.example.com:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://infra1.example.com:2379 \
--listen-client-urls http://0.0.0.0:2379 \
--listen-peer-urls http://0.0.0.0:2380
$ etcd --name infra2 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://infra2.example.com:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://infra2.example.com:2379 \
--listen-client-urls http://0.0.0.0:2379 \
--listen-peer-urls http://0.0.0.0:2380

ドメイン名の代わりにIPアドレスを使って、クラスターをブートストラップすることもできます。

$ etcd --name infra0 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://10.0.1.10:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://10.0.1.10:2379 \
--listen-client-urls http://10.0.1.10:2379 \
--listen-peer-urls http://10.0.1.10:2380
$ etcd --name infra1 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://10.0.1.11:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://10.0.1.11:2379 \
--listen-client-urls http://10.0.1.11:2379 \
--listen-peer-urls http://10.0.1.11:2380
$ etcd --name infra2 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://10.0.1.12:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://10.0.1.12:2379 \
--listen-client-urls http://10.0.1.12:2379 \
--listen-peer-urls http://10.0.1.12:2380

v3.1.0以降(v3.2.9を除く)、etcd --discovery-srv=example.comをTLSとともに設定すると、サーバーは、提供された証明書のSubject Alternative Name(SAN)フィールドにルートドメインexample.comが含まれる場合にのみ、ピアやクライアントを認証します。DNS SRVに関する注意 を参照してください。

ゲートウェイ

etcdゲートウェイは、ネットワークデータをetcdクラスターに転送する単純なTCPプロキシーです。詳しくはゲートウェイガイド を参照してください。

プロキシー

以前のetcdリリースでは、--proxyによるv2専用のプロキシーモード をサポートしていました。このモードは削除され、etcd 3.7では利用できません。v3 APIにはgRPCプロキシー を使用してください。

過去のv2プロキシーのデプロイについては、etcd 2.3リリースのクラスター構成ドキュメント を参照してください。

5 - Kubernetes StatefulSetとしてのetcdクラスターの実行

Kubernetes StatefulSetとしてetcdを実行する

以下では、Kubernetes StatefulSetで静的ブートストラップ手順 を実行する方法を示します。

マニフェストの例

このマニフェストには、Kubernetesに静的なetcdクラスターを配置するためのServiceとStatefulSetが含まれます。

マニフェストの内容をetcd.yamlというファイルにコピーすると、次のコマンドでクラスターに適用できます。

$ kubectl apply --filename etcd.yaml

適用後、Podの準備が整うまで待ちます。

$ kubectl get pods
NAME     READY   STATUS    RESTARTS   AGE
etcd-0   1/1     Running   0          24m
etcd-1   1/1     Running   0          24m
etcd-2   1/1     Running   0          24m

この例のコンテナにはetcdctlが含まれており、Pod内で直接呼び出せます。

$ kubectl exec -it etcd-0 -- etcdctl member list -wtable
+------------------+---------+--------+-------------------------+-------------------------+------------+
|        ID        | STATUS  |  NAME  |       PEER ADDRS        |      CLIENT ADDRS       | IS LEARNER |
+------------------+---------+--------+-------------------------+-------------------------+------------+
| 4f98c3545405a0b0 | started | etcd-2 | http://etcd-2.etcd:2380 | http://etcd-2.etcd:2379 |      false |
| a394e0ee91773643 | started | etcd-0 | http://etcd-0.etcd:2380 | http://etcd-0.etcd:2379 |      false |
| d10297b8d2f01265 | started | etcd-1 | http://etcd-1.etcd:2380 | http://etcd-1.etcd:2379 |      false |
+------------------+---------+--------+-------------------------+-------------------------+------------+

自己署名証明書を使って配置するには、## TLSで始まるコメント内の設定見出しを参照し、コメントを外せる値を確認してください。cert-managerで証明書を生成する追加の手順は、後のセクションに記載しています。

# file: etcd.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: etcd
  namespace: default
spec:
  type: ClusterIP
  clusterIP: None
  selector:
    app: etcd
  ##
  ## Ideally we would use SRV records to do peer discovery for initialization.
  ## Unfortunately discovery will not work without logic to wait for these to
  ## populate in the container. This problem is relatively easy to overcome by
  ## making changes to prevent the etcd process from starting until the records
  ## have populated. The documentation on statefulsets briefly talk about it.
  ##   https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#stable-network-id
  publishNotReadyAddresses: true
  ##
  ## The naming scheme of the client and server ports match the scheme that etcd
  ## uses when doing discovery with SRV records.
  ports:
  - name: etcd-client
    port: 2379
  - name: etcd-server
    port: 2380
  - name: etcd-metrics
    port: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  namespace: default
  name: etcd
spec:
  ##
  ## The service name is being set to leverage the service headlessly.
  ## https://kubernetes.io/docs/concepts/services-networking/service/#headless-services
  serviceName: etcd
  ##
  ## If you are increasing the replica count of an existing cluster, you should
  ## also update the --initial-cluster-state flag as noted further down in the
  ## container configuration.
  replicas: 3
  ##
  ## For initialization, the etcd pods must be available to eachother before
  ## they are "ready" for traffic. The "Parallel" policy makes this possible.
  podManagementPolicy: Parallel
  ##
  ## To ensure availability of the etcd cluster, the rolling update strategy
  ## is used. For availability, there must be at least 51% of the etcd nodes
  ## online at any given time.
  updateStrategy:
    type: RollingUpdate
  ##
  ## This is label query over pods that should match the replica count.
  ## It must match the pod template's labels. For more information, see the
  ## following documentation:
  ##   https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
  selector:
    matchLabels:
      app: etcd
  ##
  ## Pod configuration template.
  template:
    metadata:
      ##
      ## The labeling here is tied to the "matchLabels" of this StatefulSet and
      ## "affinity" configuration of the pod that will be created.
      ##
      ## This example's labeling scheme is fine for one etcd cluster per
      ## namespace, but should you desire multiple clusters per namespace, you
      ## will need to update the labeling schema to be unique per etcd cluster.
      labels:
        app: etcd
      annotations:
        ##
        ## This gets referenced in the etcd container's configuration as part of
        ## the DNS name. It must match the service name created for the etcd
        ## cluster. The choice to place it in an annotation instead of the env
        ## settings is because there should only be 1 service per etcd cluster.
        serviceName: etcd
    spec:
      ##
      ## Configuring the node affinity is necessary to prevent etcd servers from
      ## ending up on the same hardware together.
      ##
      ## See the scheduling documentation for more information about this:
      ##   https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
      affinity:
        ## The podAntiAffinity is a set of rules for scheduling that describe
        ## when NOT to place a pod from this StatefulSet on a node.
        podAntiAffinity:
          ##
          ## When preparing to place the pod on a node, the scheduler will check
          ## for other pods matching the rules described by the labelSelector
          ## separated by the chosen topology key.
          requiredDuringSchedulingIgnoredDuringExecution:
          ## This label selector is looking for app=etcd
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - etcd
            ## This topology key denotes a common label used on nodes in the
            ## cluster. The podAntiAffinity configuration essentially states
            ## that if another pod has a label of app=etcd on the node, the
            ## scheduler should not place another pod on the node.
            ##   https://kubernetes.io/docs/reference/labels-annotations-taints/#kubernetesiohostname
            topologyKey: "kubernetes.io/hostname"
      ##
      ## Containers in the pod
      containers:
      ## This example only has this etcd container.
      - name: etcd
        image: quay.io/coreos/etcd:v3.7.2
        imagePullPolicy: IfNotPresent
        ports:
        - name: etcd-client
          containerPort: 2379
        - name: etcd-server
          containerPort: 2380
        - name: etcd-metrics
          containerPort: 8080
        ##
        ## These probes will fail over TLS for self-signed certificates, so etcd
        ## is configured to deliver metrics over port 8080 further down.
        ##
        ## As mentioned in the "Monitoring etcd" page, /readyz and /livez were
        ## added in v3.5.12. Prior to this, monitoring required extra tooling
        ## inside the container to make these probes work.
        ##
        ## The values in this readiness probe should be further validated, it
        ## is only an example configuration.
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 30
        ## The values in this liveness probe should be further validated, it
        ## is only an example configuration.
        livenessProbe:
          httpGet:
            path: /livez
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        env:
        ##
        ## Environment variables defined here can be used by other parts of the
        ## container configuration. They are interpreted by Kubernetes, instead
        ## of in the container environment.
        ##
        ## These env vars pass along information about the pod.
        - name: K8S_NAMESPACE
          valueFrom:
            fieldRef:
             fieldPath: metadata.namespace
        - name: HOSTNAME
          valueFrom:
            fieldRef:
             fieldPath: metadata.name
        - name: SERVICE_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations['serviceName']
        ##
        ## Configuring etcdctl inside the container to connect to the etcd node
        ## in the container reduces confusion when debugging.
        - name: ETCDCTL_ENDPOINTS
          value: $(HOSTNAME).$(SERVICE_NAME):2379
        ##
        ## TLS client configuration for etcdctl in the container.
        ## These files paths are part of the "etcd-client-certs" volume mount.
        # - name: ETCDCTL_KEY
        #   value: /etc/etcd/certs/client/tls.key
        # - name: ETCDCTL_CERT
        #   value: /etc/etcd/certs/client/tls.crt
        # - name: ETCDCTL_CACERT
        #   value: /etc/etcd/certs/client/ca.crt
        ##
        ## Use this URI_SCHEME value for non-TLS clusters.
        - name: URI_SCHEME
          value: "http"
        ## TLS: Use this URI_SCHEME for TLS clusters.
        # - name: URI_SCHEME
        # value: "https"
        ##
        ## If you're using a different container, the executable may be in a
        ## different location. This example uses the full path to help remove
        ## ambiguity to you, the reader.
        ## Often you can just use "etcd" instead of "/usr/local/bin/etcd" and it
        ## will work because the $PATH includes a directory containing "etcd".
        command:
        - /usr/local/bin/etcd
        ##
        ## Arguments used with the etcd command inside the container.
        args:
        ##
        ## Configure the name of the etcd server.
        - --name=$(HOSTNAME)
        ##
        ## Configure etcd to use the persistent storage configured below.
        - --data-dir=/data
        ##
        ## In this example we're consolidating the WAL into sharing space with
        ## the data directory. This is not ideal in production environments and
        ## should be placed in it's own volume.
        - --wal-dir=/data/wal
        ##
        ## URL configurations are parameterized here and you shouldn't need to
        ## do anything with these.
        - --listen-peer-urls=$(URI_SCHEME)://0.0.0.0:2380
        - --listen-client-urls=$(URI_SCHEME)://0.0.0.0:2379
        - --advertise-client-urls=$(URI_SCHEME)://$(HOSTNAME).$(SERVICE_NAME):2379
        ##
        ## This must be set to "new" for initial cluster bootstrapping. To scale
        ## the cluster up, this should be changed to "existing" when the replica
        ## count is increased. If set incorrectly, etcd makes an attempt to
        ## start but fail safely.
        - --initial-cluster-state=new
        ##
        ## Token used for cluster initialization. The recommendation for this is
        ## to use a unique token for every cluster. This example parameterized
        ## to be unique to the namespace, but if you are deploying multiple etcd
        ## clusters in the same namespace, you should do something extra to
        ## ensure uniqueness amongst clusters.
        - --initial-cluster-token=etcd-$(K8S_NAMESPACE)
        ##
        ## The initial cluster flag needs to be updated to match the number of
        ## replicas configured. When combined, these are a little hard to read.
        ## Here is what a single parameterized peer looks like:
        ##   etcd-0=$(URI_SCHEME)://etcd-0.$(SERVICE_NAME):2380
        - --initial-cluster=etcd-0=$(URI_SCHEME)://etcd-0.$(SERVICE_NAME):2380,etcd-1=$(URI_SCHEME)://etcd-1.$(SERVICE_NAME):2380,etcd-2=$(URI_SCHEME)://etcd-2.$(SERVICE_NAME):2380
        ##
        ## The peer urls flag should be fine as-is.
        - --initial-advertise-peer-urls=$(URI_SCHEME)://$(HOSTNAME).$(SERVICE_NAME):2380
        ##
        ## This avoids probe failure if you opt to configure TLS.
        - --listen-metrics-urls=http://0.0.0.0:8080
        ##
        ## These are some configurations you may want to consider enabling, but
        ## should look into further to identify what settings are best for you.
        # - --auto-compaction-mode=periodic
        # - --auto-compaction-retention=10m
        ##
        ## TLS client configuration for etcd, reusing the etcdctl env vars.
        # - --client-cert-auth
        # - --trusted-ca-file=$(ETCDCTL_CACERT)
        # - --cert-file=$(ETCDCTL_CERT)
        # - --key-file=$(ETCDCTL_KEY)
        ##
        ## TLS server configuration for etcdctl in the container.
        ## These files paths are part of the "etcd-server-certs" volume mount.
        # - --peer-client-cert-auth
        # - --peer-trusted-ca-file=/etc/etcd/certs/server/ca.crt
        # - --peer-cert-file=/etc/etcd/certs/server/tls.crt
        # - --peer-key-file=/etc/etcd/certs/server/tls.key
        ##
        ## This is the mount configuration.
        volumeMounts:
        - name: etcd-data
          mountPath: /data
        ##
        ## TLS client configuration for etcdctl
        # - name: etcd-client-tls
        #   mountPath: "/etc/etcd/certs/client"
        #   readOnly: true
        ##
        ## TLS server configuration
        # - name: etcd-server-tls
        #   mountPath: "/etc/etcd/certs/server"
        #   readOnly: true
      volumes:
      ##
      ## TLS client configuration
      # - name: etcd-client-tls
      #   secret:
      #     secretName: etcd-client-tls
      #     optional: false
      ##
      ## TLS server configuration
      # - name: etcd-server-tls
      #   secret:
      #     secretName: etcd-server-tls
      #     optional: false
  ##
  ## This StatefulSet will uses the volumeClaimTemplate field to create a PVC in
  ## the cluster for each replica. These PVCs can not be easily resized later.
  volumeClaimTemplates:
  - metadata:
      name: etcd-data
    spec:
      accessModes: ["ReadWriteOnce"]
      ##
      ## In some clusters, it is necessary to explicitly set the storage class.
      ## This example will end up using the default storage class.
      # storageClassName: ""
      resources:
        requests:
          storage: 1Gi

証明書の生成

このセクションでは、Helm でcert-manager というOperatorをインストールします。

クラスターにcert-managerをインストールすると、クラスター内で自己署名証明書を生成できます。生成した証明書はSecretオブジェクトに格納され、コンテナ内にファイルとして配置できます。

cert-managerをインストールするhelmコマンドは次のとおりです。

$ helm upgrade --install --create-namespace --namespace cert-manager cert-manager cert-manager --repo https://charts.jetstack.io --set crds.enabled=true

自己署名証明書を生成するClusterIssuerの設定例です。

# file: issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned
spec:
  selfSigned: {}

このマニフェストは、ClusterIssuerの"selfsigned"を参照して、クライアント証明書とサーバー証明書のCertificateオブジェクトを作成します。dnsNamesには、cert-managerが作成する証明書に対して有効なホスト名をすべて列挙することを推奨します。

# file: certificates.yaml
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: etcd-server
  namespace: default
spec:
  secretName: etcd-server-tls
  issuerRef:
    name: selfsigned
    kind: ClusterIssuer
  commonName: etcd
  dnsNames:
  - etcd
  - etcd.default
  - etcd.default.svc.cluster.local
  - etcd-0
  - etcd-0.etcd
  - etcd-0.etcd.default
  - etcd-0.etcd.default.svc
  - etcd-0.etcd.default.svc.cluster.local
  - etcd-1
  - etcd-1.etcd
  - etcd-1.etcd.default
  - etcd-1.etcd.default.svc
  - etcd-1.etcd.default.svc.cluster.local
  - etcd-2
  - etcd-2.etcd
  - etcd-2.etcd.default
  - etcd-2.etcd.default.svc
  - etcd-2.etcd.default.svc.cluster.local
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: etcd-client
  namespace: default
spec:
  secretName: etcd-client-tls
  issuerRef:
    name: selfsigned
    kind: ClusterIssuer
  commonName: etcd
  dnsNames:
  - etcd
  - etcd.default
  - etcd.default.svc.cluster.local
  - etcd-0
  - etcd-0.etcd
  - etcd-0.etcd.default
  - etcd-0.etcd.default.svc
  - etcd-0.etcd.default.svc.cluster.local
  - etcd-1
  - etcd-1.etcd
  - etcd-1.etcd.default
  - etcd-1.etcd.default.svc
  - etcd-1.etcd.default.svc.cluster.local
  - etcd-2
  - etcd-2.etcd
  - etcd-2.etcd.default
  - etcd-2.etcd.default.svc
  - etcd-2.etcd.default.svc.cluster.local

6 - コンテナ内でのetcdクラスターの実行

静的ブートストラップを使ってDockerでetcdを実行する

このガイドでは、静的ブートストラップ手順 を使ってDockerでetcdを実行する方法を説明します。

Docker

Dockerホストの外部のクライアントにetcd APIを公開するには、コンテナのホストIPアドレスを使用します。IPアドレスの取得方法については、docker inspect を参照してください。または、docker runコマンドに--net=hostフラグを指定し、コンテナを独立したネットワークスタックに配置しない方法もあります。

単一ノードのetcdの実行

etcdを構成する際は、ホストのIPアドレスを使用します。

export NODE1=192.168.1.21

etcdデータを保存するDockerボリュームを構成します。

docker volume create --name etcd-data
export DATA_DIR="etcd-data"

etcdの最新バージョン(執筆時点ではv3.7.2)を実行します。

ETCD_VERSION=v3.7.2
REGISTRY=quay.io/coreos/etcd
# available from v3.2.5
REGISTRY=gcr.io/etcd-development/etcd

docker run \
  -p 2379:2379 \
  -p 2380:2380 \
  --volume=${DATA_DIR}:/etcd-data \
  --name etcd ${REGISTRY}:${ETCD_VERSION} \
  /usr/local/bin/etcd \
  --data-dir=/etcd-data --name node1 \
  --initial-advertise-peer-urls http://${NODE1}:2380 --listen-peer-urls http://0.0.0.0:2380 \
  --advertise-client-urls http://${NODE1}:2379 --listen-client-urls http://0.0.0.0:2379 \
  --initial-cluster node1=http://${NODE1}:2380

クラスターメンバーを一覧表示します。

etcdctl --endpoints=http://${NODE1}:2379 member list

3ノードのetcdクラスターの実行

REGISTRY=quay.io/coreos/etcd
# available from v3.2.5
REGISTRY=gcr.io/etcd-development/etcd

# For each machine
ETCD_VERSION=v3.7.2
TOKEN=my-etcd-token
CLUSTER_STATE=new
NAME_1=etcd-node-0
NAME_2=etcd-node-1
NAME_3=etcd-node-2
HOST_1=10.20.30.1
HOST_2=10.20.30.2
HOST_3=10.20.30.3
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380
DATA_DIR=/var/lib/etcd

# For node 1
THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
docker run \
  -p 2379:2379 \
  -p 2380:2380 \
  --volume=${DATA_DIR}:/etcd-data \
  --name etcd ${REGISTRY}:${ETCD_VERSION} \
  /usr/local/bin/etcd \
  --data-dir=/etcd-data --name ${THIS_NAME} \
  --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://0.0.0.0:2380 \
  --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://0.0.0.0:2379 \
  --initial-cluster ${CLUSTER} \
  --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}

# For node 2
THIS_NAME=${NAME_2}
THIS_IP=${HOST_2}
docker run \
  -p 2379:2379 \
  -p 2380:2380 \
  --volume=${DATA_DIR}:/etcd-data \
  --name etcd ${REGISTRY}:${ETCD_VERSION} \
  /usr/local/bin/etcd \
  --data-dir=/etcd-data --name ${THIS_NAME} \
  --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://0.0.0.0:2380 \
  --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://0.0.0.0:2379 \
  --initial-cluster ${CLUSTER} \
  --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}

# For node 3
THIS_NAME=${NAME_3}
THIS_IP=${HOST_3}
docker run \
  -p 2379:2379 \
  -p 2380:2380 \
  --volume=${DATA_DIR}:/etcd-data \
  --name etcd ${REGISTRY}:${ETCD_VERSION} \
  /usr/local/bin/etcd \
  --data-dir=/etcd-data --name ${THIS_NAME} \
  --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://0.0.0.0:2380 \
  --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://0.0.0.0:2379 \
  --initial-cluster ${CLUSTER} \
  --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}

APIバージョン3を使ってetcdctlを実行するには、次のようにします。

docker exec etcd /usr/local/bin/etcdctl put foo bar

ベアメタル

ベアメタル上に3ノードのetcdクラスターを構築するには、baremetalリポジトリ の例が参考になります。

証明書ボリュームのマウント

etcdのリリースコンテナには、デフォルトのルート証明書が含まれていません。ルート認証局によって信頼された証明書でHTTPSを使う場合(ディスカバリーなど)は、etcdコンテナに証明書ディレクトリをマウントしてください。

以下のrktコマンドは過去の使用例です。このv2ディスカバリー設定はetcd 3.7では使用できません。現在の環境にデプロイする場合は、上の静的ブートストラップの例を参照してください。

ETCD_VERSION=v3.7.2
REGISTRY=quay.io/coreos/etcd
# available from v3.2.5
REGISTRY=docker://gcr.io/etcd-development/etcd

rkt run \
  --insecure-options=image \
  --volume etcd-ssl-certs-bundle,kind=host,source=/etc/ssl/certs/ca-certificates.crt \
  --mount volume=etcd-ssl-certs-bundle,target=/etc/ssl/certs/ca-certificates.crt \
  ${REGISTRY}:${ETCD_VERSION} -- --name my-name \
  --initial-advertise-peer-urls http://localhost:2380 --listen-peer-urls http://localhost:2380 \
  --advertise-client-urls http://localhost:2379 --listen-client-urls http://localhost:2379 \
  --discovery https://discovery.etcd.io/c11fbcdc16972e45253491a24fcf45e1

以下のDockerの例は上で設定したNODE1を使い、静的ブートストラップによる証明書ボリュームのマウントを示します。

ETCD_VERSION=v3.7.2
REGISTRY=quay.io/coreos/etcd
# available from v3.2.5
REGISTRY=gcr.io/etcd-development/etcd

docker run \
  -p 2379:2379 \
  -p 2380:2380 \
  --volume=/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt \
  ${REGISTRY}:${ETCD_VERSION} \
  /usr/local/bin/etcd --name my-name \
  --initial-advertise-peer-urls http://${NODE1}:2380 --listen-peer-urls http://0.0.0.0:2380 \
  --advertise-client-urls http://${NODE1}:2379 --listen-client-urls http://0.0.0.0:2379 \
  --initial-cluster my-name=http://${NODE1}:2380

7 - 障害の種類

障害の種類とetcdの耐障害性

大規模なマシン環境では、障害は珍しくありません。ハードウェアやソフトウェアの不具合によって、マシンに障害が発生します。停電やネットワークの問題では、複数のマシンに同時に障害が発生します。異なる種類の障害が同時に起きることもあるため、起こり得る障害をすべて列挙することはほぼ不可能です。

このセクションでは障害を分類し、etcdがそれらに耐えるためにどのように設計されているかを説明します。ほとんどのユーザーは、個々の障害をいずれかの種類に当てはめられます。まれな障害やリカバリー不能な障害 に備え、etcdクラスターを必ずバックアップ してください。

少数のフォロワーの障害

フォロワーに障害が発生しても、障害が発生したメンバーがクラスター全体の半数未満で、リーダーが引き続き利用可能であれば、etcdクラスターは大きな支障なくリクエストを受け付け、処理を継続できます。たとえば、5メンバーのetcdクラスターでは、2つのフォロワーの障害はクラスターの動作に影響しません。ただし、クライアントは障害が発生したメンバーとの接続を失います。読み取りリクエストについては、クライアントライブラリがほかのメンバーに自動的に再接続し、ユーザーがこの中断を意識せずに済むようにすることを推奨します。運用担当者は、再接続によってほかのメンバーのシステム負荷が増加することを見込んでおく必要があります。

リーダーの障害

リーダーに障害が発生すると、etcdクラスターは新しいリーダーを自動的に選出します。障害直後に選出されるわけではありません。障害検出がタイムアウトに基づくため、新しいリーダーの選出には選挙タイムアウトとほぼ同じ時間がかかります。

リーダー選出中、クラスターは書き込みを処理できません。選出中に送られた書き込みリクエストは、新しいリーダーが選出されるまで処理キューで待機します。

古いリーダーに送信済みでも、まだコミットされていない書き込みは失われる可能性があります。新しいリーダーは、以前のリーダーの未コミットエントリーをすべて書き換えることができます。ユーザーから見ると、新しいリーダーの選出後に一部の書き込みリクエストがタイムアウトすることがあります。ただし、コミット済みの書き込みが失われることはありません。

新しいリーダーは、すべてのリースのタイムアウトを自動的に延長します。この仕組みにより、古いリーダーが付与したリースでも、付与されたTTLより前に期限切れになることを防ぎます。

過半数の障害

クラスターの過半数のメンバーに障害が発生すると、etcdクラスターは機能しなくなり、それ以上の書き込みを受け付けられません。

etcdクラスターが過半数の障害からリカバリーできるのは、過半数のメンバーが利用可能になってからです。過半数のメンバーがオンラインに戻れない場合、運用担当者は災害復旧 を開始してクラスターをリカバリーしなければなりません。

過半数のメンバーが稼働すると、etcdクラスターは新しいリーダーを自動的に選出し、正常な状態に戻ります。新しいリーダーは、すべてのリースのタイムアウトを自動的に延長します。この仕組みにより、サーバーが利用できないことによるリースの期限切れを防ぎます。

ネットワーク分断

ネットワーク分断は、少数のフォロワーの障害やリーダーの障害に似ています。分断によってetcdクラスターは、過半数のメンバーを持つ側と少数のメンバーを持つ側の2つに分かれます。過半数側は利用可能なクラスターとなり、少数側は利用できなくなります。etcdでは、メンバーの追加と削除を明示的に行い、その都度、現在の過半数のメンバーが変更を承認するため、「スプリットブレイン」は発生しません。

リーダーが過半数側にいる場合、過半数側から見ると少数のフォロワーの障害に相当します。リーダーが少数側にいる場合は、リーダーの障害に相当します。少数側のリーダーは退任し、過半数側が新しいリーダーを選出します。

ネットワーク分断が解消すると、少数側は過半数側のリーダーを自動的に認識し、自身の状態をリカバリーします。

ブートストラップ中の障害

クラスターのブートストラップは、必要なすべてのメンバーが正常に起動した場合にのみ成功します。ブートストラップ中に何らかの障害が発生した場合は、すべてのメンバーのデータディレクトリを削除し、新しいcluster-tokenまたは新しいディスカバリートークンでクラスターを再度ブートストラップしてください。

もちろん、稼働中のクラスターと同様に、ブートストラップに失敗したクラスターをリカバリーすることも可能です。ただし、リカバリーするデータがないため、そのクラスターをリカバリーするより、新しいクラスターをブートストラップするほうが、ほぼ常に時間もリソースも少なくて済みます。

8 - 災害復旧

etcd v3のスナップショットとリストア機能

etcdは、マシンの障害に耐えるよう設計されています。etcdクラスターは一時的な障害(マシンの再起動など)から自動的にリカバリーし、Nメンバーのクラスターでは最大*(N-1)/2の恒久的な障害に耐えます。ハードウェアの故障やディスク破損などでメンバーが恒久的に停止すると、そのメンバーはクラスターにアクセスできなくなります。クラスターが(N-1)/2*を超えるメンバーを恒久的に失うと、重大な障害となり、クォーラムを回復不能な形で失います。クォーラムを失うと、クラスターはコンセンサスに達することができず、更新を受け付け続けられません。

重大な障害からリカバリーするため、etcd v3はv3のキーデータを失わずにクラスターを再作成するスナップショットとリストア機能を提供します。v2のキーをリカバリーするには、v2管理ガイド を参照してください。

キー空間のスナップショット作成

クラスターのリカバリーには、まずetcdメンバーのキー空間のスナップショットが必要です。稼働中のメンバーからetcdctl snapshot saveコマンドで取得する方法と、etcdのデータディレクトリからmember/snap/dbファイルをコピーする方法があります。たとえば次のコマンドは、$ENDPOINTが提供するキー空間のスナップショットをsnapshot.dbファイルに保存します。

$ ETCDCTL_API=3 etcdctl --endpoints $ENDPOINT snapshot save snapshot.db

member/snap/dbファイルからスナップショットを取得すると、wal(先行書き込みログ)フォルダーには含まれていても、まだ書き込まれていないデータが失われる場合がある点に注意してください。

スナップショットの状態

スナップショットに含まれるリビジョンとハッシュを確認するには、etcdutl snapshot statusコマンドを使用できます。

$ etcdutl snapshot status snapshot.db -w table
+---------+----------+------------+------------+
|  HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
+---------+----------+------------+------------+
| 7ef846e |   485261 |      11642 |      94 MB |
+---------+----------+------------+------------+

クラスターのリストア

リビジョンの差

クラスターをリストアすると、既存のクライアントにはリビジョンが数百、数千も前に戻ったように見えることがあります。スナップショットには取得時点までのデータ履歴しか含まれず、現在の状態はそれより先に進んでいる可能性があるためです。

これは、etcdを使ってKubernetesを実行している場合に特に問題になります。コントローラーやOperatorは、ローカルキャッシュとして機能し、監視で更新通知を受けるinformersを使用することがあります。古いリビジョンにリストアすると、キャッシュが正しく更新されず、コントローラーが予測不能で不整合な動作をする可能性があります。

監視APIの利用者がいることがわかっている場合、etcdデータをローカルにキャッシュしている場合、またはKubernetesを使用している場合は、スナップショットからのリストアに、後述の「リビジョンの引き上げ」を使うことを強く推奨します。

スナップショットからのリストア

クラスターのリストアに必要なのは、1つのスナップショット「db」ファイルだけです。etcdutl snapshot restoreによるリストアは、新しいetcdデータディレクトリを作成します。すべてのメンバーで同じスナップショットを使ってリストアすることを推奨します。リストア時には、スナップショットの一部のメタデータ(具体的にはメンバーIDとクラスターID)を上書きし、メンバーは以前の識別情報を失います。この上書きによって、新しいメンバーが既存のクラスターに誤って参加することを防ぎます。したがって、スナップショットからクラスターを起動するには、新しい論理クラスターとしてリストアしなければなりません。

単純なリストアは次のように実行できます。

$ etcdutl snapshot restore snapshot.db --data-dir output-dir

整合性チェック

リストア時には、任意でスナップショットの整合性を検証できます。etcdctl snapshot saveで取得したスナップショットには整合性ハッシュが含まれ、etcdutl snapshot restoreが検証します。データディレクトリからコピーしたスナップショットには整合性ハッシュがないため、リストアには--skip-hash-checkが必要です。

リビジョンを引き上げてリストア

リストア後にリビジョンが減少しないよう、--bump-revisionオプションを指定できます。このオプションは64ビット整数を受け取り、スナップショットの現在のリビジョンに加える数を表します。etcdへの書き込みごとにリビジョンは1増えるため、毎秒の書き込みが1500未満であれば、1'000'000'000引き上げることで1週間前のスナップショットにも対応できます。

Kubernetesコントローラーを使う場合は、--mark-compactedで引き上げ分も含めたすべてのリビジョンをコンパクション済みとしてマークすることも重要です。これにより、すべての監視を終了させ、スナップショット取得後に生じたリビジョンについてのリクエストにetcdが応答しないようにして、informerのキャッシュを事実上無効化します。

呼び出し全体の例は次のとおりです。

$ etcdutl snapshot restore snapshot.db --bump-revision 1000000000 --mark-compacted --data-dir output-dir

メンバー構成を更新してリストア

etcdクラスターのメンバー情報はetcd自身に保存され、Raftコンセンサスアルゴリズムで管理されます。クォーラムを完全に失った場合、たとえばまったく新しいメンバー群を使うなど、新しいクラスターをどこでどのように構成するかを見直すこともできます。

スナップショットからリストアするときは、次のように新しいメンバー構成をデータストアに直接指定できます。

$ etcdutl snapshot restore snapshot.db \
  --name m1 \
  --data-dir m1.etcd \
  --initial-cluster m1=http://host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host1:2380

これにより、新しく構成したクラスターは、指定されたトークンでリストアしたほかのメンバーだけに接続し、まだ稼働していて接続を試みる可能性がある古いメンバーには接続しません。

別の方法として、etcdの起動時に--force-new-clusterを指定し、既存のアプリケーションデータを保持したままメンバー構成を上書きできます。ただし、以前のクラスターのほかのメンバーがまだ稼働しているとパニックになるため、この方法は強く非推奨です。スナップショットを必ず定期的に保存してください。

一連の操作例

稼働中のクラスターから、次のようにスナップショットを取得します。

$ etcdctl snapshot save snapshot.db

前の例に続いて、次のコマンドで3メンバーのクラスター用の新しいetcdデータディレクトリ(m1.etcd、m2.etcd、m3.etcd)を作成します。

$ etcdutl snapshot restore snapshot.db \
  --name m1 \
  --data-dir m1_data_dir.etcd \
  --initial-cluster m1=http://host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host1:2380
$ etcdutl snapshot restore snapshot.db \
  --name m2 \
  --data-dir m2_data_dir.etcd \
  --initial-cluster m1=http://host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host2:2380
$ etcdutl snapshot restore snapshot.db \
  --name m3 \
  --data-dir m3_data_dir.etcd \
  --initial-cluster m1=http://host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host3:2380

次に、新しいデータディレクトリを指定してetcdを起動します。

$ etcd \
  --name m1 \
  --data-dir m1_data_dir.etcd \
  --listen-client-urls http://host1:2379 \
  --advertise-client-urls http://host1:2379 \
  --listen-peer-urls http://host1:2380 &
$ etcd \
  --name m2 \
  --data-dir m2_data_dir.etcd \
  --listen-client-urls http://host2:2379 \
  --advertise-client-urls http://host2:2379 \
  --listen-peer-urls http://host2:2380 &
$ etcd \
  --name m3 \
  --data-dir m3_data_dir.etcd \
  --listen-client-urls http://host3:2379 \
  --advertise-client-urls http://host3:2379 \
  --listen-peer-urls http://host3:2380 &

これで、リストアしたetcdクラスターが利用可能になり、スナップショットのキー空間を提供しているはずです。

etcd v3.6以降、スナップショットの取得にはetcdctlだけを使用でき、スナップショットからのリストアにはetcdutlを使用します。--data-dirを指定しない場合、--data-dirのデフォルト値は<name>.etcdです(<name>は--nameの値)。たとえば、--data-dirを指定せず、メンバー名がm1、m2、m3であれば、--data-dirのディレクトリはm1.etcd、m2.etcd、m3.etcdになります。

9 - etcdゲートウェイ

etcdゲートウェイの用途と設定方法

etcdゲートウェイとは

etcdゲートウェイは、ネットワークデータをetcdクラスターに転送する単純なTCPプロキシーです。状態を持たず、透過的に動作し、クライアントのリクエストを検査したりクラスターのレスポンスに介入したりしません。TLS接続の終端、クライアントに代わるTLSハンドシェイク、接続が保護されているかの検証も行いません。

ゲートウェイは複数のetcdサーバーのエンドポイントに対応し、単純なラウンドロビン方式で動作します。利用可能なエンドポイントだけに転送し、クライアントが障害を意識せずに済むようにします。将来は、重み付きラウンドロビンなど、ほかの再試行方針に対応する可能性もあります。

etcdゲートウェイを使う場面

etcdにアクセスするすべてのアプリケーションは、まずetcdクラスターのクライアントエンドポイントのアドレスを把握しなければなりません。同じサーバー上の複数のアプリケーションが同じetcdクラスターにアクセスする場合も、それぞれがクラスターの通知するクライアントエンドポイントを知る必要があります。再構成によってetcdクラスターのエンドポイントが変わると、すべてのアプリケーションでエンドポイント一覧の更新が必要になる可能性があります。この広範囲の再構成は手間がかかり、誤りも生じやすい操作です。

etcdゲートウェイは、安定したローカルエンドポイントとして機能することで、この問題を解決します。典型的な構成では、各マシンでローカルアドレスをリッスンするゲートウェイを動かし、etcdを使う各アプリケーションはローカルのゲートウェイに接続します。その結果、個々のアプリケーションを更新せず、ゲートウェイのエンドポイントを更新するだけで済みます。

つまり、同じetcdクラスターにアクセスする複数のアプリケーションを実行する各マシンでetcdゲートウェイを動かすことで、クラスターのエンドポイント変更を自動的に反映できます。

etcdゲートウェイが適さない場面

  • 性能の改善

ゲートウェイは、etcdクラスターの性能改善を目的として設計されていません。キャッシュ、監視の集約、バッチ処理は提供しません。etcdチームは、クラスターのスケーラビリティーを向上させるためのキャッシュプロキシーを開発しています。

  • クラスター管理システム上での実行

Kubernetesのような高度なクラスター管理システムは、サービスディスカバリーを標準で提供します。アプリケーションは、システムが管理するDNS名や仮想IPアドレスでetcdクラスターにアクセスできます。たとえば、kube-proxyはetcdゲートウェイに相当します。

etcdゲートウェイの起動

次の静的エンドポイントを持つetcdクラスターを考えます。

名前 アドレス ホスト名 ポート
infra0 10.0.1.10 infra0.example.com 2379
infra1 10.0.1.11 infra1.example.com 2379
infra2 10.0.1.12 infra2.example.com 2379

次のコマンドで、これらの静的エンドポイントを使用するetcdゲートウェイを起動します。

$ etcd gateway start --endpoints=infra0.example.com:2379,infra1.example.com:2379,infra2.example.com:2379
2016-08-16 11:21:18.867350 I | tcpproxy: ready to proxy client requests to [...]

DNSでサービスディスカバリーを行う場合は、次のDNS SRVエントリーを考えます。

$ dig +noall +answer SRV _etcd-client._tcp.example.com
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra0.example.com.
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra1.example.com.
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra2.example.com.
$ dig +noall +answer infra0.example.com infra1.example.com infra2.example.com
infra0.example.com.  300  IN  A  10.0.1.10
infra1.example.com.  300  IN  A  10.0.1.11
infra2.example.com.  300  IN  A  10.0.1.12

次のコマンドで、DNS SRVエントリーからエンドポイントを取得するetcdゲートウェイを起動します。

$ etcd gateway start --discovery-srv=example.com
2016-08-16 11:21:18.867350 I | tcpproxy: ready to proxy client requests to [...]

設定フラグ

etcdクラスター

–endpoints

  • クライアント接続の転送先となるetcdサーバーを、カンマ区切りで指定します。
  • デフォルト:127.0.0.1:2379
  • ポートを含めなければなりません。
  • 無効な例:https://127.0.0.1:2379(ゲートウェイはTLSを終端しません)。ゲートウェイはHTTPスキームの検証やリクエストの検査を行わず、指定されたエンドポイントにリクエストを転送するだけである点に注意してください。

–discovery-srv

  • SRVレコードを通じてクラスターのエンドポイントをブートストラップするためのDNSドメイン。
  • デフォルト:(未設定)

ネットワーク

–listen-addr

  • クライアントのリクエストを受け付けるためにバインドするインターフェースとポート。
  • デフォルト:127.0.0.1:23790

–retry-delay

  • 障害が発生したエンドポイントへの再接続を試みるまでの待機時間。
  • デフォルト:1m0s
  • 無効な例:“123”(時間の単位を含む形式が必要)

セキュリティー

–insecure-discovery

  • 安全でない、または中間者攻撃を受けるおそれのあるSRVレコードを受け入れます。
  • デフォルト:false

–trusted-ca-file

  • SRVディスカバリーが返すエンドポイントを検証するための、etcdクラスターのクライアントTLS CAファイルのパス。このファイルは発見したエンドポイントの認証にのみ使い、データ転送用の接続を作成するためには使わない点に注意してください。ゲートウェイはTLS接続を終端せず、クライアントに代わってTLS接続を作成することもありません。
  • デフォルト:(未設定)

10 - gRPCプロキシー

gRPC層で動作するステートレスなetcdリバースプロキシー

gRPCプロキシーは、gRPC層(L7)で動作するステートレスなetcdリバースプロキシーです。中核となるetcdクラスターの処理負荷を減らすために設計されています。水平スケーラビリティーのために、監視APIとリースAPIのリクエストを集約します。また、過剰なリクエストを送るクライアントからクラスターを守るため、キー範囲のリクエストをキャッシュします。

gRPCプロキシーは、複数のetcdサーバーのエンドポイントに対応します。起動時に使用するエンドポイントをランダムに1つ選び、その障害を検出するまで、すべてのリクエストをそのエンドポイントで処理します。障害を検出すると、ほかに利用可能なエンドポイントがあれば切り替え、クライアントが障害を意識せずに済むようにします。将来は、重み付きラウンドロビンなど、ほかの再試行方針に対応する可能性もあります。

スケーラブルな監視API

gRPCプロキシーは、同じキーや範囲を監視する複数のクライアントウォッチャー(c-watchers)を、etcdサーバーに接続する1つのウォッチャー(s-watcher)に集約します。プロキシーは、s-watcherからのすべてのイベントをc-watchersに配信します。

N個のクライアントが同じキーを監視するとき、1つのgRPCプロキシーでetcdサーバーの監視負荷をNから1に減らせます。複数のgRPCプロキシーを配置すれば、サーバーの負荷をさらに分散できます。

次の例では、3つのクライアントがキーAを監視します。gRPCプロキシーは3つのウォッチャーを集約し、etcdサーバーに接続する1つのウォッチャーを作成します。

            +-------------+
            | etcd server |
            +------+------+
                   ^ watch key A (s-watcher)
                   |
           +-------+-----+
           | gRPC proxy  | <-------+
           |             |         |
           ++-----+------+         |watch key A (c-watcher)
watch key A ^     ^ watch key A    |
(c-watcher) |     | (c-watcher)    |
    +-------+-+  ++--------+  +----+----+
    |  client |  |  client |  |  client |
    |         |  |         |  |         |
    +---------+  +---------+  +---------+

制限事項

複数のクライアントウォッチャーを効率よく1つに集約するため、gRPCプロキシーは可能であれば、新しいc-watchersを既存のs-watcherにまとめます。この集約先のs-watcherは、ネットワークの遅延やバッファー内の未配信イベントによって、etcdサーバーと同期していないことがあります。監視するリビジョンを指定しない場合、gRPCプロキシーはc-watcherがストアの最新リビジョンから監視を開始することを保証しません。たとえば、リビジョン1000のetcdサーバーをクライアントが監視すると、リビジョン1000から始まります。一方、gRPCプロキシーを介して監視すると、リビジョン990から始まる場合があります。

取り消しにも同様の制限があります。ウォッチャーを取り消したとき、etcdサーバーのリビジョンが取り消しレスポンスのリビジョンより大きい場合があります。

この2つの制限は、ほとんどの用途では問題にならないはずです。将来は、より正確なリビジョンのレスポンスを得るため、ウォッチャーにgRPCプロキシーを迂回させる追加オプションが用意される可能性があります。

スケーラブルなリースAPI

リースを維持するには、クライアントはetcdサーバーへのgRPCストリームを少なくとも1つ確立し、定期的にハートビートを送信しなければなりません。多くのクライアントにまたがって大量のリース操作が行われる場合、これらのストリームがCPUの過剰な使用につながることがあります。中核クラスターのストリーム総数を減らすため、プロキシーはリースストリームの集約に対応しています。

N個のクライアントがリースを更新するとき、1つのgRPCプロキシーでetcdサーバーのストリーム負荷をNから1に減らせます。gRPCプロキシーを追加し、複数のプロキシーにストリームをさらに分散することもできます。

次の例では、3つのクライアントが独立した3つのリース(L1、L2、L3)を更新します。gRPCプロキシーは、3つのクライアント側リースストリーム(c-streams)を、etcdサーバーに接続する1つのリース維持ストリーム(s-stream)に集約します。プロキシーは、クライアント側のリースハートビートをc-streamsからs-streamに転送し、対応するc-streamsにレスポンスを返します。

          +-------------+
          | etcd server |
          +------+------+
                 ^
                 | heartbeat L1, L2, L3
                 | (s-stream)
                 v
         +-------+-----+
         | gRPC proxy  +<-----------+
         +---+------+--+            | heartbeat L3
             ^      ^               | (c-stream)
heartbeat L1 |      | heartbeat L2  |
(c-stream)   v      v (c-stream)    v
      +------+-+  +-+------+  +-----+--+
      | client |  | client |  | client |
      +--------+  +--------+  +--------+

過剰なリクエストを送るクライアントからの保護

gRPCプロキシーは、一貫性の要件に違反しない場合に、リクエストのレスポンスをキャッシュします。これにより、高速なforループでリクエストを繰り返すクライアントからetcdサーバーを保護できます。

etcd gRPCプロキシーの起動

次の静的エンドポイントを持つetcdクラスターを考えます。

名前 アドレス ホスト名
infra0 10.0.1.10 infra0.example.com
infra1 10.0.1.11 infra1.example.com
infra2 10.0.1.12 infra2.example.com

次のコマンドで、これらの静的エンドポイントを使用するetcd gRPCプロキシーを起動します。

$ etcd grpc-proxy start --endpoints=infra0.example.com,infra1.example.com,infra2.example.com --listen-addr=127.0.0.1:2379

etcd gRPCプロキシーは起動し、ポート2379でリッスンします。クライアントのリクエストは、上記の3つのエンドポイントのいずれかに転送されます。

プロキシーを通じてリクエストを送信します。

$ ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 put foo bar
OK
$ ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 get foo
foo
bar

クライアントエンドポイントの同期と名前解決

プロキシーは、ユーザー定義のエンドポイントに書き込むことで、自身のエンドポイントをディスカバリー用に登録できます。これには2つの目的があります。1つは、クライアントがプロキシーのエンドポイント群と自身のエンドポイントを同期し、高可用性を得ることです。もう1つは、etcdのgRPC名前解決 にエンドポイントを提供することです。

ユーザー定義の接頭辞を指定して、プロキシーを登録します。

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23790 \
  --advertise-client-url=127.0.0.1:23790 \
  --resolver-prefix="___grpc_proxy_endpoint" \
  --resolver-ttl=60

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23791 \
  --advertise-client-url=127.0.0.1:23791 \
  --resolver-prefix="___grpc_proxy_endpoint" \
  --resolver-ttl=60

メンバー一覧を要求すると、プロキシーは自身のすべてのメンバーを返します。

ETCDCTL_API=3 etcdctl --endpoints=http://localhost:23790 member list --write-out table

+----+---------+--------------------------------+------------+-----------------+
| ID | STATUS  |              NAME              | PEER ADDRS |  CLIENT ADDRS   |
+----+---------+--------------------------------+------------+-----------------+
|  0 | started | Gyu-Hos-MBP.sfo.coreos.systems |            | 127.0.0.1:23791 |
|  0 | started | Gyu-Hos-MBP.sfo.coreos.systems |            | 127.0.0.1:23790 |
+----+---------+--------------------------------+------------+-----------------+

これにより、クライアントはSyncを通じてプロキシーのエンドポイントを自動検出できます。

cli, err := clientv3.New(clientv3.Config{
    Endpoints: []string{"http://localhost:23790"},
})
if err != nil {
    log.Fatal(err)
}
defer cli.Close()

// fetch registered grpc-proxy endpoints
if err := cli.Sync(context.Background()); err != nil {
    log.Fatal(err)
}

一方、リゾルバーの接頭辞を指定せずにプロキシーを構成すると、

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23792 \
  --advertise-client-url=127.0.0.1:23792

grpc-proxyのメンバー一覧APIは、自身のadvertise-client-urlを返します。

ETCDCTL_API=3 etcdctl --endpoints=http://localhost:23792 member list --write-out table

+----+---------+--------------------------------+------------+-----------------+
| ID | STATUS  |              NAME              | PEER ADDRS |  CLIENT ADDRS   |
+----+---------+--------------------------------+------------+-----------------+
|  0 | started | Gyu-Hos-MBP.sfo.coreos.systems |            | 127.0.0.1:23792 |
+----+---------+--------------------------------+------------+-----------------+

名前空間

アプリケーションがキー空間全体を完全に制御できることを想定していても、etcdクラスターはほかのアプリケーションと共有している場合があります。すべてのアプリケーションが互いに干渉せずに動作できるよう、プロキシーはキー空間を分割し、各クライアントからはキー空間全体にアクセスできるように見せられます。--namespaceフラグを指定すると、プロキシーに入るすべてのクライアントリクエストのキーに、ユーザー定義の接頭辞を付けます。etcdクラスターへのアクセスはその接頭辞の配下に限定し、プロキシーが返すレスポンスからは接頭辞を取り除きます。クライアントには、接頭辞が存在しないように見えます。

プロキシーに名前空間を設定するには、--namespaceを指定して起動します。

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23790 \
  --namespace=my-prefix/

以後、プロキシーへのアクセスには、etcdクラスター上で透過的に接頭辞が付きます。

$ ETCDCTL_API=3 etcdctl --endpoints=localhost:23790 put my-key abc
# OK
$ ETCDCTL_API=3 etcdctl --endpoints=localhost:23790 get my-key
# my-key
# abc
$ ETCDCTL_API=3 etcdctl --endpoints=localhost:2379 get my-prefix/my-key
# my-prefix/my-key
# abc

TLSの終端

gRPCプロキシーで暗号化されていないローカルエンドポイントを提供し、保護されたetcdクラスターからのTLSを終端します。

試すには、まずクライアント接続にHTTPSを使う単一メンバーのetcdクラスターを起動します。

$ etcd --listen-client-urls https://localhost:2379 --advertise-client-urls https://localhost:2379 --cert-file=peer.crt --key-file=peer.key --trusted-ca-file=ca.crt --client-cert-auth

クライアントポートがHTTPSを提供していることを確認します。

# fails
$ ETCDCTL_API=3 etcdctl --endpoints=http://localhost:2379 endpoint status
# works
$ ETCDCTL_API=3 etcdctl --endpoints=https://localhost:2379 --cert=client.crt --key=client.key --cacert=ca.crt endpoint status

次に、クライアント証明書を使ってetcdエンドポイントhttps://localhost:2379に接続し、localhost:12379でgRPCプロキシーを起動します。

$ etcd grpc-proxy start --endpoints=https://localhost:2379 --listen-addr localhost:12379 --cert client.crt --key client.key --cacert=ca.crt --insecure-skip-tls-verify &

最後に、HTTPでプロキシーにキーを書き込み、TLSの終端をテストします。

$ ETCDCTL_API=3 etcdctl --endpoints=http://localhost:12379 put abc def
# OK

メトリクスと健全性

gRPCプロキシーは、--endpointsで指定したetcdメンバーの/healthとPrometheusの/metricsエンドポイントを公開します。--metrics-addrフラグで追加のURLを定義し、/metricsと/healthの両方に応答させることもできます。

$ etcd grpc-proxy start \
  --endpoints https://localhost:2379 \
  --metrics-addr https://0.0.0.0:4443 \
  --listen-addr 127.0.0.1:23790 \
  --key client.key \
  --key-file proxy-server.key \
  --cert client.crt \
  --cert-file proxy-server.crt \
  --cacert ca.pem \
  --trusted-ca-file proxy-ca.pem

既知の問題

プロキシーのメインインターフェースは、HTTP2とHTTP/1.1の両方を提供します。上記の例のようにTLSを設定したプロキシーのリッスンインターフェースにcURLなどのクライアントでアクセスする場合、/metricsや/healthを取得するには、リクエストでプロトコルをHTTP/1.1に明示的に設定する必要があります。--metrics-addrフラグによる追加インターフェースには、この要件はありません。

 $ curl --cacert proxy-ca.pem --key proxy-client.key --cert proxy-client.crt https://127.0.0.1:23790/metrics --http1.1

11 - ハードウェアの推奨事項

etcdクラスターを管理するためのハードウェアの指針

開発やテストでは、etcdは通常、限られたリソースでも問題なく動作します。ノートPCや安価なクラウドマシンでetcdを使って開発することも一般的です。ただし、etcdクラスターを本番運用する場合は、適切に管理するうえでハードウェアの指針が役立ちます。ここでの提案は厳格な規則ではなく、堅牢な本番環境を構築する出発点です。どの環境でも、本番運用前に想定ワークロードを再現してテストすることを推奨します。

CPU

大きなCPU処理能力を必要とするetcd環境は多くありません。一般的なクラスターは、2〜4コアあれば円滑に動作します。 数千のクライアントや毎秒数万のリクエストを処理する高負荷のetcd環境では、etcdがメモリからリクエストを処理できるため、CPUがボトルネックになりがちです。このような高負荷環境では、通常8〜16個の専用コアが必要です。

メモリ

etcdのメモリ使用量は比較的小さいものの、性能を発揮するには十分なメモリが必要です。etcdサーバーはキー・バリューデータを積極的にキャッシュし、残りのメモリの大半をウォッチャーの追跡に使います。通常は8GBで十分です。数千のウォッチャーと数百万のキーがある高負荷環境では、状況に応じて16GB〜64GBのメモリを割り当ててください。

ディスク

高速なディスクは、etcd環境の性能と安定性を左右する最も重要な要素です。

低速なディスクはetcdのリクエストのレイテンシーを増加させ、クラスターの安定性を損なう可能性があります。etcdのコンセンサスプロトコルはメタデータをログに永続化することに依存するため、過半数のクラスターメンバーがすべてのリクエストをディスクに書き込まなければなりません。さらにetcdは、このログを切り詰められるよう、自身の状態をディスクに増分でチェックポイント保存します。これらの書き込みに時間がかかりすぎると、ハートビートがタイムアウトして選挙が発生し、クラスターの安定性が損なわれることがあります。ディスクがetcdに十分な速度かどうかは、通常fio などのベンチマークツールで確認できます。例はこちら を参照してください。

etcdはディスクの書き込みレイテンシーの影響を非常に強く受けます。通常、逐次50 IOPS(たとえば7200 RPMのディスク)が必要です。高負荷のクラスターには、逐次500 IOPS(たとえば一般的なローカルSSDや高性能な仮想ブロックデバイス)を推奨します。多くのクラウドプロバイダーは逐次IOPSではなく並行IOPSを公表しており、公表値が逐次IOPSの10倍になる場合がある点に注意してください。実際の逐次IOPSを測定するには、diskbench やfio などのディスクベンチマークツールを推奨します。

etcdに必要なディスク帯域幅はそれほど大きくありませんが、帯域幅が大きいほど、障害が発生したメンバーがクラスターに追いつく際のリカバリー時間を短縮できます。通常、10MB/sあれば100MBのデータを15秒以内にリカバリーできます。大規模なクラスターでは、1GBのデータを15秒以内にリカバリーするため、100MB/s以上を推奨します。

可能であれば、etcdのストレージにはSSDを使用してください。SSDは通常、回転式ディスクより書き込みレイテンシーが低く、その変動も小さいため、etcdの安定性と信頼性が向上します。回転式ディスクを使う場合は、できる限り高速なもの(15,000 RPM)を選んでください。回転式ディスクでもSSDでも、RAID 0はディスク速度を上げる有効な方法です。クラスターメンバーが3つ以上ある場合、etcdの整合性を保ったレプリケーションで高可用性を実現できるため、ミラーリングやパリティーを使うRAIDは不要です。

ネットワーク

複数のメンバーを持つetcd環境では、高速で信頼性の高いネットワークが役立ちます。etcdは一貫性と分断耐性を両立させるため、分断を伴う障害が起こる信頼性の低いネットワークでは、可用性が低下します。レイテンシーが低ければ、etcdメンバー間で素早く通信できます。帯域幅が大きければ、障害が発生したメンバーのリカバリー時間を短縮できます。一般的なetcd環境には1GbEで十分です。大規模なetcdクラスターでは、10GbEネットワークで平均リカバリー時間を短縮できます。

レイテンシーの増加を避け、分断の可能性を減らすため、可能であればetcdメンバーを1つのデータセンター内に配置してください。別のデータセンターに障害ドメインを置く必要がある場合は、既存のデータセンターに近い場所を選んでください。複数のデータセンターにまたがる配置については、チューニング のドキュメントも参照してください。

ハードウェア構成例

以下に、AWSとGCE環境でのハードウェア構成例を示します。繰り返しになりますが、運用担当者は本番運用前に、想定ワークロードを再現してetcd環境をテストすることを推奨します。

これらの構成は、マシンをetcd専用に使うことを前提としています。同じマシンでほかのアプリケーションを動かすと、リソース競合が発生し、クラスターが不安定になる可能性があります。

小規模クラスター

小規模クラスターは、クライアント数100未満、毎秒のリクエスト数200未満で、保存するデータは100MB以下です。

アプリケーションのワークロード例:50ノードのKubernetesクラスター

プロバイダー タイプ vCPUs メモリ(GB) 最大並行IOPS ディスク帯域幅(MB/s)
AWS m4.large 2 8 3600 56.25
GCE n1-standard-2 + 50GB PD SSD 2 7.5 1500 25

中規模クラスター

中規模クラスターは、クライアント数500未満、毎秒のリクエスト数1,000未満で、保存するデータは500MB以下です。

アプリケーションのワークロード例:250ノードのKubernetesクラスター

プロバイダー タイプ vCPUs メモリ(GB) 最大並行IOPS ディスク帯域幅(MB/s)
AWS m4.xlarge 4 16 6000 93.75
GCE n1-standard-4 + 150GB PD SSD 4 15 4500 75

大規模クラスター

大規模クラスターは、クライアント数1,500未満、毎秒のリクエスト数10,000未満で、保存するデータは1GB以下です。

アプリケーションのワークロード例:1,000ノードのKubernetesクラスター

プロバイダー タイプ vCPUs メモリ(GB) 最大並行IOPS ディスク帯域幅(MB/s)
AWS m4.2xlarge 8 32 8000 125
GCE n1-standard-8 + 250GB PD SSD 8 30 7500 125

特大規模クラスター

特大規模クラスターは、クライアント数が1,500を超え、毎秒のリクエスト数が10,000を超え、1GBを超えるデータを保存します。

アプリケーションのワークロード例:3,000ノードのKubernetesクラスター

プロバイダー タイプ vCPUs メモリ(GB) 最大並行IOPS ディスク帯域幅(MB/s)
AWS m4.4xlarge 16 64 16,000 250
GCE n1-standard-16 + 500GB PD SSD 16 60 15,000 250

12 - 保守

etcdクラスターの定期保守ガイド

概要

etcdクラスターの信頼性を維持するには、定期的な保守が必要です。etcdを使うアプリケーションの要件にもよりますが、通常は保守を自動化でき、停止や大幅な性能低下なしに実施できます。

etcdの保守はすべて、キー空間が消費するストレージリソースを管理するためのものです。キー空間のサイズを十分に管理できない場合に備え、ストレージ容量のクォータが設けられています。etcdメンバーの空き容量が少なくなると、クォータによってクラスター全体にアラームが発報され、システムは操作が制限された保守モードに移行します。キー空間への書き込み領域が不足しないよう、キー空間の履歴をコンパクションしなければなりません。ストレージ領域そのものは、etcdメンバーのデフラグメンテーションで回収できます。また、メンバーの状態のスナップショットを定期的にバックアップしておくと、操作ミスによる意図しない論理的なデータ損失や破損からリカバリーできます。

Raftログの保持

etcd --snapshot-countは、コンパクション前にメモリに保持する、適用済みRaftエントリーの数を設定します。--snapshot-countに達すると、サーバーはまずスナップショットのデータをディスクに永続化し、その後で古いエントリーを切り詰めます。遅いフォロワーがコンパクション済みのインデックスより前のログを要求すると、リーダーはスナップショットを送信し、フォロワーの状態を上書きさせます。

--snapshot-countを大きくすると、スナップショットまでに多くのRaftエントリーをメモリに保持するため、メモリ使用量の増大が繰り返し発生します 。リーダーが最新のRaftエントリーを長く保持する分、遅いフォロワーはリーダーのスナップショット作成前に追いつく時間を長く取れます。--snapshot-countは、メモリ使用量の増加と遅いフォロワーの可用性向上とのトレードオフです。

v3.2では、--snapshot-countのデフォルト値が10,000から100,000に変更されました 。v3.7.0のデフォルト値は10,000です。

性能面では、--snapshot-countを100,000より大きくすると、書き込みスループットに影響する可能性があります。メモリ上のオブジェクトが増えると、Go GCのマーク段階であるruntime.scanobject が遅くなることがあり、メモリの回収頻度が下がると割り当ても遅くなります。性能はワークロードとシステム環境によって異なりますが、一般にコンパクションが頻繁すぎるとクラスターの可用性と書き込みスループットに影響します。頻度が低すぎても、Goのガベージコレクターに過大な負荷をかけるため有害です。詳しい調査結果は、etcdとRaftの性能特性の理解 を参照してください。

履歴のコンパクション:v3 APIキー・バリューデータベース

etcdはキー空間の履歴を正確に保持するため、性能低下や最終的なストレージ容量の枯渇を防ぐには、履歴を定期的にコンパクションすることを推奨します。キー空間の履歴をコンパクションすると、指定したリビジョンより前に置き換えられたキーの情報をすべて破棄します。これらのキーが使っていた領域は、キー空間への追加書き込みに利用できるようになります。

キー空間は、etcdの時間範囲に基づく履歴保持ポリシーで自動的にコンパクションすることも、etcdctlで手動実行することもできます。etcdctlでは処理を細かく制御できます。一方、自動コンパクションは、一定期間のキー履歴だけを必要とするアプリケーションに適しています。

etcdctlから開始するコンパクションは、次のように行います。

# compact up to revision 3
$ etcdctl compact 3

コンパクション対象のリビジョンより前のリビジョンにはアクセスできなくなります。

$ etcdctl get --rev=2 somekey
Error:  rpc error: code = 11 desc = etcdserver: mvcc: required revision has been compacted

自動コンパクション

--auto-compaction-modeと--auto-compaction-retentionオプションで、キー空間を自動的にコンパクションするようetcdを設定できます。モードはperiodic(デフォルト)とrevisionの2種類です。

定期コンパクション

定期コンパクションでは、時間に基づく範囲のキー空間履歴を保持します。

# keep one hour of history
$ etcd --auto-compaction-retention=1h

保持値は、履歴をどれだけ残すかを指定します。レコードは、作成からおよそその期間が経過するまでコンパクションされません。そのため、遅いウォッチャーでも保持期間内であれば追いつくことができます。

保持期間が1時間より長い場合、etcdは保持期間全体の履歴を維持しながら、1時間ごとにコンパクションします。保持期間が1時間以下の場合は、その保持期間と同じ間隔でコンパクションします。

たとえば--auto-compaction-retention=10hでは、etcdは最初のコンパクションまで10時間待機し、その後は1時間ごとにコンパクションします。

0hr  (rev = 1)
1hr  (rev = 10)
...
8hr  (rev = 80)
9hr  (rev = 90)
10hr (rev = 100, Compact(1))
11hr (rev = 110, Compact(10))
...

推奨値は用途によって異なります。

  • 同じキーを頻繁に更新する場合:1hや30mなどの短い期間
  • 更新が少ない場合:24h、48h、72hなどの長い期間
  • 汎用的なデフォルト:10h

リビジョンによるコンパクション

リビジョンによるコンパクションでは、固定数のリビジョンを保持します。

# keep 1000 revisions
$ etcd --auto-compaction-mode=revision --auto-compaction-retention=1000

etcdは5分ごとに確認し、"latest revision" - 1000のリビジョンを対象にコンパクションします。たとえば最新リビジョンが30000の場合、リビジョン29000を対象にコンパクションします。

デフラグメンテーション

キー空間をコンパクションした後、バックエンドデータベースに内部断片化が生じる場合があります。内部断片化した領域はバックエンドが自由に再利用できますが、ストレージ容量を消費し続けます。古いリビジョンのコンパクションは、バックエンドデータベースに隙間を残すことでetcdの内部断片化を引き起こします。この断片化した領域はetcdには利用できても、ホストのファイルシステムには利用できません。つまり、アプリケーションのデータを削除しても、ディスク領域は回収されません。

デフラグメンテーションは、このストレージ領域をファイルシステムに返します。クラスター全体でレイテンシーが急増するのを避けられるよう、メンバー単位で実行します。

etcdメンバーをデフラグメンテーションするには、etcdctl defragコマンドを使用します。

$ etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]
警告

稼働中のメンバーに対するデフラグメンテーションは、状態の再構築中、システムのデータ読み書きをブロックする点に注意してください。

警告

デフラグメンテーションのリクエストは、クラスター内で複製されません。リクエストはローカルノードだけに適用されます。--endpointsフラグにすべてのメンバーを指定するか、--clusterフラグでクラスターメンバーをすべて自動検出してください。

デフォルトのエンドポイントが属するクラスター内のすべてのエンドポイントに対して、デフラグメンテーションを実行します。

$ etcdctl defrag --cluster
Finished defragmenting etcd member[http://127.0.0.1:2379]
Finished defragmenting etcd member[http://127.0.0.1:22379]
Finished defragmenting etcd member[http://127.0.0.1:32379]

etcdが停止している間にデータディレクトリを直接デフラグメンテーションするには、次のコマンドを使用します。

etcdutl defrag --data-dir <path-to-etcd-data-dir>

容量クォータ

etcdの容量クォータは、クラスターの信頼性のある動作を確保します。クォータがなければ、etcdのキー空間が大きくなりすぎて性能が低下したり、ストレージ容量が尽きてクラスターが予測不能な動作をしたりする可能性があります。いずれかのメンバーでキー空間のバックエンドデータベースが容量クォータを超えると、etcdはクラスター全体にアラームを発報し、キーの読み取りと削除だけを受け付ける保守モードに移行します。キー空間に十分な空きを確保し、バックエンドデータベースをデフラグメンテーションし、容量クォータのアラームを解除して初めて、クラスターは通常の動作に戻れます。

etcdはデフォルトで、ほとんどのアプリケーションに適した控えめな容量クォータを設定します。コマンドラインで、バイト単位の値を設定することもできます。

# set a very small 16 MiB quota
$ etcd --quota-backend-bytes=$((16*1024*1024))

次のループで容量クォータに達する状態を作れます。

# fill keyspace
$ while [ 1 ]; do dd if=/dev/urandom bs=1024 count=1024  | ETCDCTL_API=3 etcdctl put key  || break; done
...
Error:  rpc error: code = 8 desc = etcdserver: mvcc: database space exceeded
# confirm quota space is exceeded
$ ETCDCTL_API=3 etcdctl --write-out=table endpoint status
+----------------+------------------+-----------+---------+-----------+-----------+------------+
|    ENDPOINT    |        ID        |  VERSION  | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+----------------+------------------+-----------+---------+-----------+-----------+------------+
| 127.0.0.1:2379 | bf9071f4639c75cc | 2.3.0+git | 18 MB   | true      |         2 |       3332 |
+----------------+------------------+-----------+---------+-----------+-----------+------------+
# confirm alarm is raised
$ ETCDCTL_API=3 etcdctl alarm list
memberID:13803658152347727308 alarm:NOSPACE

キー空間の余分なデータを削除し、バックエンドデータベースをデフラグメンテーションすると、クラスターの使用量をクォータの範囲内に戻せます。

# get current revision
$ rev=$(ETCDCTL_API=3 etcdctl --endpoints=:2379 endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*')
# compact away all old revisions
$ ETCDCTL_API=3 etcdctl compact $rev
compacted revision 1516
# defragment away excessive space
$ ETCDCTL_API=3 etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]
# disarm alarm
$ ETCDCTL_API=3 etcdctl alarm disarm
memberID:13803658152347727308 alarm:NOSPACE
# test puts are allowed again
$ ETCDCTL_API=3 etcdctl put newkey 123
OK

メトリクスetcd_mvcc_db_total_size_in_use_in_bytesは、履歴コンパクション後の実際のデータベース使用量を表します。一方、etcd_debugging_mvcc_db_total_size_in_bytesは、デフラグメンテーションを待つ空き領域を含むデータベースのサイズを表します。後者が増えるのは前者が後者に近づいたときだけです。つまり、両方がクォータに近い場合、容量クォータに達するのを防ぐため、履歴のコンパクションが必要です。

v3.4以降、etcd_debugging_mvcc_db_total_size_in_bytesはetcd_mvcc_db_total_size_in_bytesに改名されています。

警告

Put/Txn/LeaseGrantリクエストでErrGRPCNoSpaceエラーが返っても、バックエンドでは書き込みが成功している場合があります。etcdはAPI層と内部のApply層で容量クォータをチェックしますが、Apply層はNOSPACEアラームを発報するだけで、トランザクションの進行をブロックしないためです。

スナップショットのバックアップ

etcdクラスターのスナップショットを定期的に作成すると、キー空間の永続的なバックアップになります。メンバーのバックエンドデータベースのスナップショットを定期的に取得することで、etcdクラスターを正常であると確認できた時点にリカバリーできます。

スナップショットはetcdctlで取得します。

$ etcdctl snapshot save backup.db
$ etcdutl --write-out=table snapshot status backup.db
+----------+----------+------------+------------+
|   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| fe01cf57 |       10 |          7 | 2.1 MB     |
+----------+----------+------------+------------+

13 - etcdの監視

システムの健全性確認とクラスターのデバッグのためのetcd監視

各etcdサーバーは、クライアントポートのHTTPエンドポイントを通じて、ローカルの監視情報を提供します。監視データは、システムの健全性確認とクラスターのデバッグの両方に役立ちます。

デバッグエンドポイント

--log-level=debugを設定すると、etcdサーバーはクライアントポートの/debugパスでデバッグ情報を公開します。性能が低下し、ログ出力が多くなるため、--log-level=debugの設定には注意してください。

/debug/pprofは、Goランタイムの標準的なプロファイリング用エンドポイントです。CPU、ヒープ、ミューテックス、goroutineの使用状況を調査できます。たとえば次のgo tool pprofは、etcdが時間を費やしている関数の上位10件を取得します。

$ go tool pprof http://localhost:2379/debug/pprof/profile
Fetching profile from http://localhost:2379/debug/pprof/profile
Please wait... (30s)
Saved profile in /home/etcd/pprof/pprof.etcd.localhost:2379.samples.cpu.001.pb.gz
Entering interactive mode (type "help" for commands)
(pprof) top10
310ms of 480ms total (64.58%)
Showing top 10 nodes out of 157 (cum >= 10ms)
    flat  flat%   sum%        cum   cum%
   130ms 27.08% 27.08%      130ms 27.08%  runtime.futex
    70ms 14.58% 41.67%       70ms 14.58%  syscall.Syscall
    20ms  4.17% 45.83%       20ms  4.17%  github.com/coreos/etcd/vendor/golang.org/x/net/http2/hpack.huffmanDecode
    20ms  4.17% 50.00%       30ms  6.25%  runtime.pcvalue
    20ms  4.17% 54.17%       50ms 10.42%  runtime.schedule
    10ms  2.08% 56.25%       10ms  2.08%  github.com/coreos/etcd/vendor/github.com/coreos/etcd/etcdserver.(*EtcdServer).AuthInfoFromCtx
    10ms  2.08% 58.33%       10ms  2.08%  github.com/coreos/etcd/vendor/github.com/coreos/etcd/etcdserver.(*EtcdServer).Lead
    10ms  2.08% 60.42%       10ms  2.08%  github.com/coreos/etcd/vendor/github.com/coreos/etcd/pkg/wait.(*timeList).Trigger
    10ms  2.08% 62.50%       10ms  2.08%  github.com/coreos/etcd/vendor/github.com/prometheus/client_golang/prometheus.(*MetricVec).hashLabelValues
    10ms  2.08% 64.58%       10ms  2.08%  github.com/coreos/etcd/vendor/golang.org/x/net/http2.(*Framer).WriteHeaders

/debug/requestsエンドポイントでは、WebブラウザーでgRPCトレースと性能統計を確認できます。次は、キーabcに対するRangeリクエストの例です。

When	Elapsed (s)
2017/08/18 17:34:51.999317 	0.000244 	/etcdserverpb.KV/Range
17:34:51.999382 	 .    65 	... RPC: from 127.0.0.1:47204 deadline:4.999377747s
17:34:51.999395 	 .    13 	... recv: key:"abc"
17:34:51.999499 	 .   104 	... OK
17:34:51.999535 	 .    36 	... sent: header:<cluster_id:14841639068965178418 member_id:10276657743932975437 revision:15 raft_term:17 > kvs:<key:"abc" create_revision:6 mod_revision:14 version:9 value:"asda" > count:1

メトリクスエンドポイント

各etcdサーバーは、クライアントポートの/metricsパスでメトリクスを公開します。必要に応じて、--listen-metrics-urlsで指定した場所にも公開します。

メトリクスはcurlで取得できます。

$ curl -L http://localhost:2379/metrics | grep -v debugging # ignore unstable debugging metrics

# HELP etcd_disk_backend_commit_duration_seconds The latency distributions of commit called by backend.
# TYPE etcd_disk_backend_commit_duration_seconds histogram
etcd_disk_backend_commit_duration_seconds_bucket{le="0.002"} 72756
etcd_disk_backend_commit_duration_seconds_bucket{le="0.004"} 401587
etcd_disk_backend_commit_duration_seconds_bucket{le="0.008"} 405979
etcd_disk_backend_commit_duration_seconds_bucket{le="0.016"} 406464
...

ヘルスチェック

v3.3.0以降、--listen-metrics-urlsで指定した場所は、/metricsに加えて/healthエンドポイントにも応答します。これは、標準のエンドポイントに相互(クライアント)TLS認証を設定していても、ロードバランサーや監視サービスがヘルスチェックにアクセスする必要がある場合に役立ちます。

v3.4以降、/livezと/readyzの2つのエンドポイントが追加されています。

  • /livezは、プロセスが生存しているか、再起動が必要かを示します。
  • /readyzは、プロセスがトラフィックを処理できる状態かを示します。

エンドポイントの設計の詳細は、KEP に記載されています。

各エンドポイントには複数の個別のヘルスチェックが含まれます。verboseパラメーターを使うと、チェックの詳細と状態を出力できます。例:

curl -k http://localhost:2379/readyz?verbose

次のようなレスポンスが返ります。

[+]data_corruption ok
[+]serializable_read ok
[+]linearizable_read ok
ok

HTTP APIでは、特定のチェックを除外することもできます。例:

curl -k http://localhost:2379/readyz?exclude=data_corruption

Prometheus

etcdのメトリクスを取り込んで記録する最も簡単な方法は、Prometheus の監視サービスを実行することです。

まず、Prometheusをインストールします。

PROMETHEUS_VERSION="2.0.0"
wget https://github.com/prometheus/prometheus/releases/download/v$PROMETHEUS_VERSION/prometheus-$PROMETHEUS_VERSION.linux-amd64.tar.gz -O /tmp/prometheus-$PROMETHEUS_VERSION.linux-amd64.tar.gz
tar -xvzf /tmp/prometheus-$PROMETHEUS_VERSION.linux-amd64.tar.gz --directory /tmp/ --strip-components=1
/tmp/prometheus -version

Prometheusのスクレイパーの対象に、etcdクラスターのエンドポイントを設定します。

cat > /tmp/test-etcd.yaml <<EOF
global:
  scrape_interval: 10s
scrape_configs:
  - job_name: test-etcd
    static_configs:
    - targets: ['10.240.0.32:2379','10.240.0.33:2379','10.240.0.34:2379']
EOF
cat /tmp/test-etcd.yaml

Prometheusのハンドラーを設定します。

nohup /tmp/prometheus \
    -config.file /tmp/test-etcd.yaml \
    -web.listen-address ":9090" \
    -storage.local.path "test-etcd.data" >> /tmp/test-etcd.log  2>&1 &

これで、Prometheusは10秒ごとにetcdのメトリクスを収集します。

アラート

etcd v3クラスター向けに、Prometheusのデフォルトアラート が用意されています。

注記

用途に応じてjobラベルの調整が必要になる場合があります。これらのルールは単一のクラスターに適用するよう作られているため、クラスター固有のラベルを選ぶことを推奨します。

Grafana

Grafana にはPrometheus対応が組み込まれているため、Prometheusデータソースを追加するだけで使えます。

Name:   test-etcd
Type:   Prometheus
Url:    http://localhost:9090
Access: proxy

次に、デフォルトのetcdダッシュボードテンプレート をインポートし、カスタマイズします。たとえば、Prometheusデータソース名がmy-etcdであれば、JSONのdatasourceフィールドの値もmy-etcdにする必要があります。

ダッシュボードの例:

分散トレーシング

etcdはv3.5で、OpenTelemetry を使った分散トレーシングに対応しました。

注記

etcd 3.7では、以下のトレーシング用フラグから以前のexperimental接頭辞を除いたものを使用してください。

分散トレーシングを有効にするには、etcdサーバーに--enable-distributed-tracing=trueを指定します。また、--distributed-tracing-sampling-rate=<number>フラグで、100万スパンあたりに収集するサンプル数を指定します。デフォルトのサンプリングレートは0です。

分散トレーシングを構成するには、次の任意のフラグを指定してetcdサーバーを起動します。

  • --distributed-tracing-address - (任意)- “localhost:4317” - トレースコレクターのアドレス。

  • --distributed-tracing-service-name - (任意)- “etcd” - 分散トレーシングのサービス名。すべてのetcdインスタンスで同じにしなければなりません。

  • --distributed-tracing-instance-id - (任意)- インスタンスID。任意ですが、設定を強く推奨します。etcdインスタンスごとに一意でなければなりません。

分散トレーシングを有効にする前に、OpenTelemetryのエンドポイントを用意してください。アドレスがデフォルトと異なる場合は、--distributed-tracing-addressフラグで上書きします。OpenTelemetryにはさまざまな実行方法があるため、詳しくはコレクターのドキュメント を参照してください。

注記

ほかの可観測性シグナルと同様に、リソースのオーバーヘッドがあります。初期の測定では、CPUのオーバーヘッドは2%〜4%になる可能性があります。

14 - 性能

性能を理解する:レイテンシーとスループット

性能を理解する

etcdは、高い性能を安定して持続的に提供します。性能を決める要素は、レイテンシーとスループットの2つです。レイテンシーは操作の完了にかかる時間で、スループットは一定期間内に完了する操作の総数です。etcdがクライアントのリクエストを並行して受け付ける場合、通常は全体のスループットが増えるにつれて平均レイテンシーも増加します。Google Compute Engine(GCE)の標準的なn-4や同等のAWSマシンタイプといった一般的なクラウド環境では、3メンバーのetcdクラスターは軽い負荷で1ミリ秒未満にリクエストを完了し、高負荷では毎秒30,000を超えるリクエストを完了できます。

etcdは、Raftコンセンサスアルゴリズムを使ってメンバー間でリクエストを複製し、合意に達します。コンセンサスの性能、特にコミットのレイテンシーは、ネットワークIOとディスクIOのレイテンシーという2つの物理的制約を受けます。etcdリクエストの完了に必要な最短時間は、メンバー間のネットワーク往復時間(RTT)と、fdatasyncがデータを永続ストレージにコミットする時間の合計です。データセンター内のRTTでも数百マイクロ秒かかる場合があります。米国内の一般的なRTTは約50msで、大陸間では400msほどかかる場合もあります。回転式ディスクの一般的なfdatasyncレイテンシーは約10msです。SSDでは、通常1ms未満です。etcdはスループットを向上させるため、複数のリクエストをまとめてRaftに渡します。このバッチ処理方針により、高負荷でも高いスループットを実現できます。

etcd全体の性能には、ほかのサブシステムも影響します。シリアライズされた各リクエストは、boltdbを基盤とするetcdのMVCCストレージエンジンを通る必要があり、通常は数十マイクロ秒かかります。etcdは、最近適用したリクエストの増分スナップショットを定期的に作成し、以前のディスク上のスナップショットにマージします。この処理によって、レイテンシーが急増する場合があります。SSDでは通常問題になりませんが、HDDでは観測されるレイテンシーが2倍になることがあります。同様に、実行中のコンパクションもetcdの性能に影響します。ただし、通常のリクエストとリソースを奪い合わないようコンパクションを分散して実行するため、その影響は多くの場合わずかです。RPCシステムのgRPCは、明確に定義された拡張可能なAPIをetcdに提供しますが、特にローカル読み取りでは追加のレイテンシーも生じます。

ベンチマーク

etcdに付属するbenchmark CLIツールで、etcdの性能を測定できます。

基準となる性能値を示すため、次のハードウェア構成の3メンバーのetcdクラスターを考えます。

  • Google Cloud Compute Engine
  • 8 vCPUs + 16GBメモリ + 50GB SSDのマシン3台
  • 16 vCPUs + 30GBメモリ + 50GB SSDのマシン1台(クライアント)
  • Ubuntu 17.04
  • etcd 3.2.0、go 1.8.3

この構成でのetcdの書き込み性能は、およそ次のとおりです。

キー数 キーサイズ(バイト) 値サイズ(バイト) 接続数 クライアント数 対象etcdサーバー 平均書き込みQPS リクエストあたりの平均レイテンシー サーバーの平均RSS
10,000 8 256 1 1 リーダーのみ 583 1.6ms 48 MB
100,000 8 256 100 1000 リーダーのみ 44,341 22ms 124MB
100,000 8 256 100 1000 全メンバー 50,104 20ms 126MB

コマンド例は次のとおりです。

# write to leader
benchmark --endpoints=${HOST_1} --target-leader --conns=1 --clients=1 \
    put --key-size=8 --sequential-keys --total=10000 --val-size=256
benchmark --endpoints=${HOST_1} --target-leader  --conns=100 --clients=1000 \
    put --key-size=8 --sequential-keys --total=100000 --val-size=256

# write to all members
benchmark --endpoints=${HOST_1},${HOST_2},${HOST_3} --conns=100 --clients=1000 \
    put --key-size=8 --sequential-keys --total=100000 --val-size=256

線形化可能読み取りリクエストは、最新のデータを取得するため、クラスターメンバーのクォーラムを通じてコンセンサスを得ます。直列化可能読み取りリクエストは、クォーラムではなく任意の単一etcdメンバーが処理するため、線形化可能読み取りより低コストですが、古いデータを返す可能性があります。etcdの読み取り性能は次のとおりです。

リクエスト数 キーサイズ(バイト) 値サイズ(バイト) 接続数 クライアント数 一貫性 平均読み取りQPS リクエストあたりの平均レイテンシー
10,000 8 256 1 1 線形化可能 1,353 0.7ms
10,000 8 256 1 1 直列化可能 2,909 0.3ms
100,000 8 256 100 1000 線形化可能 141,578 5.5ms
100,000 8 256 100 1000 直列化可能 185,758 2.2ms

コマンド例は次のとおりです。

# Single connection read requests
benchmark --endpoints=${HOST_1},${HOST_2},${HOST_3} --conns=1 --clients=1 \
    range YOUR_KEY --consistency=l --total=10000
benchmark --endpoints=${HOST_1},${HOST_2},${HOST_3} --conns=1 --clients=1 \
    range YOUR_KEY --consistency=s --total=10000

# Many concurrent read requests
benchmark --endpoints=${HOST_1},${HOST_2},${HOST_3} --conns=100 --clients=1000 \
    range YOUR_KEY --consistency=l --total=100000
benchmark --endpoints=${HOST_1},${HOST_2},${HOST_3} --conns=100 --clients=1000 \
    range YOUR_KEY --consistency=s --total=100000

新しい環境に初めてetcdクラスターを構築する際は、十分な性能が得られることを確認するため、ベンチマークテストの実行を推奨します。クラスターのレイテンシーとスループットは、環境のわずかな違いにも影響されることがあります。

15 - 実行時再構成の設計

etcdの実行時再構成コマンドの設計

実行時再構成は、分散システム、とりわけetcdのようにコンセンサスに基づくシステムで、実装が最も難しく、誤りが生じやすい機能の1つです。

ここでは、etcdの実行時再構成コマンドの設計と、これらの問題への取り組みを説明します。

2段階の構成変更でクラスターの安全性を確保

etcdでは、安全性のため、すべての実行時再構成を2段階 で行う必要があります。たとえばメンバーを追加する場合は、まず新しい構成をクラスターに通知し、その後に新しいメンバーを起動します。

第1段階 - 新しい構成をクラスターに通知

etcdクラスターにメンバーを追加するには、APIを呼び出して、新しいメンバーの追加をクラスターに要求します。既存のクラスターに新しいメンバーを追加する方法はこれだけです。構成変更にクラスターが合意すると、API呼び出しが戻ります。

第2段階 - 新しいメンバーを起動

新しいetcdメンバーを既存のクラスターに参加させるには、正しいinitial-clusterを指定し、initial-cluster-stateをexistingに設定します。メンバーは起動時にまず既存のクラスターに接続し、現在のクラスター構成がinitial-clusterに指定された期待する構成と一致することを確認します。新しいメンバーが正常に起動すれば、クラスターは期待する構成に到達しています。

処理を明確な2段階に分けることで、ユーザーはクラスターメンバーの変更を明示的に行う必要があります。これにより、実際には柔軟性が増し、動作も把握しやすくなります。たとえば、etcdクラスターの既存メンバーと同じIDを持つ新しいメンバーを追加しようとすると、第1段階ですぐに失敗し、稼働中のクラスターには影響しません。誤って新しいメンバーを追加することも、同様の仕組みで防ぎます。クラスターが構成変更を受け入れる前に新しいetcdメンバーが参加しようとしても、クラスターは受け入れません。

クラスターメンバーの変更に明示的な手順がなければ、etcdでは意図しないメンバー変更が起こり得ます。たとえばsystemdなどのinitシステムの管理下でetcdを実行している場合、メンバーシップAPIで削除されたetcdが再起動し、起動時にクラスターへの再参加を試みます。障害時にetcdを再起動するようsystemdを設定していると、APIでメンバーを削除するたびにこのサイクルが繰り返され、意図しない動作になります。

実行時再構成は頻繁には行わない操作を想定しています。構成の安全性を確保し、明示的な管理の下でクラスターを常に円滑に稼働させるため、ユーザーが主導する明示的な操作としました。

クォーラムを恒久的に失った場合は新しいクラスターが必要

クラスターが過半数のメンバーを恒久的に失った場合、以前の状態をリカバリーするには、古いデータディレクトリから新しいクラスターを起動する必要があります。

障害が発生したメンバーを既存のクラスターから強制的に削除してリカバリーすることは、技術的には可能です。ただし、この方法は通常のコンセンサスによるコミット段階を迂回してしまい安全でないため、サポートしないことにしました。削除するメンバーが実際には停止していない場合や、同じクラスターの異なるメンバーを通じて強制削除した場合、同じclusterIDを持つクラスターの状態が分岐してしまいます。これは非常に危険で、後から原因を調査して修復することも困難です。

正しく配置されていれば、過半数を恒久的に失う可能性は非常に低くなります。それでも、特別な配慮が必要なほど重大な問題です。etcdを本番運用する前に、災害復旧のドキュメント を読み、過半数の恒久的な喪失に備えることを強く推奨します。

実行時再構成に公開ディスカバリーサービスを使わない

公開ディスカバリーサービスは、クラスターのブートストラップにのみ使用することを推奨します。既存のクラスターにメンバーを参加させるには、実行時再構成APIを使用してください。

ディスカバリーサービスは、すべてのメンバーのIPアドレスが事前にわからないクラウド環境で、etcdクラスターをブートストラップするために設計されています。クラスターのブートストラップが完了すると、すべてのメンバーのIPアドレスがわかるため、技術的にはディスカバリーサービスは不要になるはずです。

公開ディスカバリーサービスにはすべてのクラスター構成情報があるため、実行時再構成にも使うと便利に思えます。しかし、公開ディスカバリーサービスに依存すると、次の問題が生じます。

  1. ブートストラップ時だけでなく、クラスターのライフサイクル全体に外部依存が生まれます。クラスターと公開ディスカバリーサービスの間でネットワークの問題が発生すると、クラスターも影響を受けます。

  2. 公開ディスカバリーサービスは、ライフサイクル全体を通じてクラスターの正しい実行時構成を反映しなければなりません。不正な操作を防ぐセキュリティーの仕組みが必要で、その実現は困難です。

  3. 公開ディスカバリーサービスは、数万のクラスター構成を保持する必要があります。私たちの公開ディスカバリーサービスのバックエンドは、その負荷に対応できる状態ではありません。

実行時再構成に対応するディスカバリーサービスが必要な場合は、専用の非公開サービスを構築するのが最適です。

16 - 実行時再構成

etcdの段階的な実行時再構成

etcdは段階的な実行時再構成をサポートしており、稼働中にクラスターのメンバー構成を更新できます。

再構成リクエストを処理できるのは、クラスターメンバーの過半数が稼働している場合だけです。本番環境では、クラスターを常に2を超えるメンバーで構成することを強く推奨します。2メンバーのクラスターからメンバーを削除するのは安全ではありません。2メンバーのクラスターの過半数も2だからです。削除処理中に障害が発生すると、クラスターが処理を継続できなくなり、過半数の障害から再起動 する必要が生じる可能性があります。

実行時再構成の設計について理解を深めるには、実行時再構成の設計ドキュメント を参照してください。

再構成の用途

このセクションでは、クラスターを再構成する主な理由を説明します。多くの場合、後述のクラスター再構成の操作 で説明するメンバーの追加と削除を組み合わせるだけで対応できます。

複数マシンの入れ替えやアップグレード

計画保守(ハードウェアのアップグレード、ネットワーク停止など)で複数のクラスターメンバーを移動する必要がある場合は、1つずつ変更することを推奨します。

リーダーの削除は安全ですが、選挙中に短い停止時間が発生します。クラスターが50MBを超えるv2データを保持している場合は、メンバーのデータディレクトリの移行 を推奨します。

クラスターサイズの変更

クラスターサイズを大きくすると、耐障害性 と読み取り性能を向上できます。クライアントは任意のメンバーから読み取れるため、メンバーを増やすと、直列化可能読み取りの全体的なスループットが向上します。

クラスターサイズを小さくすると、耐障害性が低下する一方で、書き込み性能を向上できます。クラスターへの書き込みは、過半数のメンバーに複製されてからコミット済みと見なされます。クラスターサイズが小さくなると、過半数に必要な数が減るため、各書き込みをより早くコミットできます。

障害が発生したマシンの置き換え

ハードウェアの故障、データディレクトリの破損、その他の致命的な問題でマシンが停止した場合は、できるだけ早い置き換えを推奨します。障害が発生していても削除されていないマシンはクォーラムに悪影響を与え、追加の障害への耐性を低下させます。

マシンを置き換えるには、クラスターからのメンバー削除 の手順に従い、その後で代わりとなる新しいメンバーを追加 します。クラスターが50MBを超えるデータを保持しており、障害が発生したメンバーのデータディレクトリにまだアクセスできる場合は、そのデータディレクトリの移行 を推奨します。

過半数の障害からクラスターを再起動

クラスターの過半数を失った場合や、すべてのノードのIPアドレスが変わった場合は、安全なリカバリーのために手動操作が必要です。基本的な手順は、古いデータから新しいクラスターを作成 し、単一メンバーを強制的にリーダーとして動作させ、最後に実行時構成でそのクラスターに新しいメンバーを1つずつ追加 することです。

少数の障害からクラスターをリカバリー

特定のメンバーを失った場合は、障害が発生したマシンの置き換えと同じです。手順は障害が発生したマシンの置き換え に記載しています。

クラスター再構成の操作

上記の用途を踏まえて、それぞれの操作を説明します。

変更を行う前に、etcdメンバーの単純過半数(クォーラム)が利用可能でなければなりません。これは、etcdへのあらゆる書き込みに必要な条件と基本的に同じです。

クラスターへの変更は、すべて順番に行わなければなりません。

  • 単一メンバーのpeerURLsを更新するには、更新操作を実行する
  • 正常な単一メンバーを置き換えるには、古いメンバーを削除してから新しいメンバーを追加する
  • メンバーを3から5に増やすには、追加操作を2回実行する
  • メンバーを5から3に減らすには、削除操作を2回実行する

これらの例ではすべて、etcdに付属するコマンドラインツールetcdctlを使用します。etcdctlを使わずにメンバー構成を変更するには、v3 gRPCメンバーAPI を使用してください。

メンバーの更新

通知するクライアントURLの更新

メンバーが通知するクライアントURLを更新するには、クライアントURLのフラグ(--advertise-client-urls)または環境変数(ETCD_ADVERTISE_CLIENT_URLS)を変更して、そのメンバーを再起動するだけです。再起動したメンバーは、更新後のURLを自動的に公開します。クライアントURLの更新を誤っても、etcdクラスターの健全性には影響しません。

通知するピアURLの更新

メンバーが通知するピアURLを更新するには、まずmemberコマンドで明示的に更新し、その後でメンバーを再起動します。ピアURLの更新はクラスター全体の構成を変え、健全性に影響する可能性があるため、この追加操作が必要です。

通知するピアURLを更新するには、まず対象メンバーのIDを確認します。etcdctlで全メンバーを一覧表示するには、次のようにします。

$ etcdctl member list
6e3bd23ae5f1eae0: name=node2 peerURLs=http://localhost:23802 clientURLs=http://127.0.0.1:23792
924e2e83e93f2560: name=node3 peerURLs=http://localhost:23803 clientURLs=http://127.0.0.1:23793
a8266ecf031671f3: name=node1 peerURLs=http://localhost:23801 clientURLs=http://127.0.0.1:23791

この例では、メンバーID a8266ecf031671f3をupdateし、そのpeerURLsをhttp://10.0.1.10:2380に変更します。

$ etcdctl member update a8266ecf031671f3 --peer-urls=http://10.0.1.10:2380
Updated member with ID a8266ecf031671f3 in cluster

メンバーの削除

削除するメンバーIDがa8266ecf031671f3であるとします。removeコマンドで削除します。

$ etcdctl member remove a8266ecf031671f3
Removed member a8266ecf031671f3 from cluster

対象メンバーはこの時点で自身を停止し、削除されたことをログに出力します。

etcd: this member has been permanently removed from the cluster. Exiting.

リーダーの削除は安全ですが、新しいリーダーの選出中はクラスターが動作しません。この時間は、通常、選挙タイムアウトと投票処理の時間の合計です。

新しいメンバーの追加

メンバーの追加は、2段階で行います。

  • v3 gRPCメンバーAPI またはetcdctl member addコマンドで、新しいメンバーをクラスターに追加します。
  • 更新後のメンバー一覧(既存メンバーと新しいメンバー)を含む新しいクラスター構成で、新しいメンバーを起動します。

etcdctlでは、メンバーの名前 と通知するピアURL を指定して、クラスターに新しいメンバーを追加します。

$ etcdctl member add infra3 --peer-urls=http://10.0.1.13:2380
added member 9bf1b35fc7761a23 to cluster

ETCD_NAME="infra3"
ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra3=http://10.0.1.13:2380"
ETCD_INITIAL_CLUSTER_STATE=existing

etcdctlは新しいメンバーをクラスターに通知し、正常に起動するために必要な環境変数を出力しました。続いて、新しいメンバーに対応するフラグで新しいetcdプロセスを起動します。

$ export ETCD_NAME="infra3"
$ export ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra3=http://10.0.1.13:2380"
$ export ETCD_INITIAL_CLUSTER_STATE=existing
$ etcd --listen-client-urls http://10.0.1.13:2379 --advertise-client-urls http://10.0.1.13:2379 --listen-peer-urls http://10.0.1.13:2380 --initial-advertise-peer-urls http://10.0.1.13:2380 --data-dir %data_dir%

新しいメンバーはクラスターの一員として稼働し、直ちにほかのメンバーへの追従を開始します。

複数のメンバーを追加する場合は、1つずつ構成し、正しく起動することを確認してから次を追加するのがベストプラクティスです。1ノードのクラスターに新しいメンバーを追加すると、コンセンサスに必要な過半数が2メンバーになるため、新しいメンバーが起動するまでクラスターは処理を進められません。この動作は、etcdctl member addが新しいメンバーをクラスターに通知してから、新しいメンバーが既存メンバーへの接続を確立するまでの間だけ発生します。

新しいメンバーを学習者メンバーとして追加

v3.4以降、etcdは新しいメンバーを学習者メンバー、つまり投票しないメンバーとして追加できます。 その背景と設計は、設計ドキュメント を参照してください。 追加処理の安全性を高め、追加時のクラスター停止時間を減らすため、新しいメンバーは追従が完了するまで学習者メンバーとして追加することを推奨します。手順は次の3段階です。

  • gRPCメンバーAPI またはetcdctl member add --learnerコマンドで、新しいメンバーを学習者メンバーとして追加します。

  • 更新後のメンバー一覧(既存メンバーと新しいメンバー)を含む新しいクラスター構成で、新しいメンバーを起動します。 この手順は前述とまったく同じです。

  • gRPCメンバーAPI またはetcdctl member promoteコマンドで、新しく追加した学習者メンバーを投票メンバーに昇格させます。 etcdサーバーは、操作の安全性を確保するため、昇格リクエストを検証します。 学習者メンバーは、そのRaftログがリーダーに追いついてからでなければ、投票メンバーに昇格できません。 リーダーのRaftログに追いついていない場合、昇格リクエストは失敗します(詳しくはメンバー昇格時のエラー を参照)。 この場合は、待ってから再試行することを推奨します。

v3.4では、etcdサーバーはクラスター内の学習者メンバー数を1つに制限します。主な目的は、リーダーから学習者メンバーへのデータ配信による、リーダーの追加負荷を抑えることです。

etcdctl member addに--learnerフラグを指定し、新しいメンバーを学習者メンバーとしてクラスターに追加します。

$ etcdctl member add infra3 --peer-urls=http://10.0.1.13:2380 --learner
Member 9bf1b35fc7761a23 added to cluster a7ef944b95711739

ETCD_NAME="infra3"
ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra3=http://10.0.1.13:2380"
ETCD_INITIAL_CLUSTER_STATE=existing

新しく追加した学習者メンバーのetcdプロセスを起動した後、etcdctl member promoteで投票メンバーに昇格させます。

$ etcdctl member promote 9bf1b35fc7761a23
Member 9e29bbaa45d74461 promoted in cluster a7ef944b95711739

メンバー追加時のエラー

次の例では、新しいホストがノード一覧に含まれていません。新しいクラスターの場合は、初期クラスターメンバーの一覧にこのノードを追加しなければなりません。

$ etcd --name infra3 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  --initial-cluster-state existing
etcdserver: assign ids error: the member count is unequal
exit 1

この失敗例では、登録したピアアドレス(10.0.1.13:2380)とは異なるアドレス(10.0.1.14:2380)でメンバーを起動しています。

$ etcd --name infra4 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra4=http://10.0.1.14:2380 \
  --initial-cluster-state existing
etcdserver: assign ids error: unmatched member while checking PeerURLs
exit 1

削除済みメンバーのデータディレクトリを使ってetcdを起動すると、クラスター内の稼働中メンバーに接続した時点で、etcdは自動的に終了します。

$ etcd
etcd: this member has been permanently removed from the cluster. Exiting.
exit 1

学習者メンバー追加時のエラー

すでに学習者メンバーが1つあるクラスターには、学習者メンバーを追加できません(v3.4)。

$ etcdctl member add infra4 --peer-urls=http://10.0.1.14:2380 --learner
Error: etcdserver: too many learner members in cluster

学習者メンバー昇格時のエラー

学習者メンバーを投票メンバーに昇格できるのは、リーダーと同期している場合だけです。

$ etcdctl member promote 9bf1b35fc7761a23
Error: etcdserver: can only promote a learner member which is in sync with leader

学習者メンバーでないメンバーの昇格は失敗します。

$ etcdctl member promote 9bf1b35fc7761a23
Error: etcdserver: can only promote a learner member

クラスターに存在しないメンバーの昇格は失敗します。

$ etcdctl member promote 12345abcde
Error: etcdserver: member not found

厳格な再構成チェックモード(-strict-reconfig-check)

前述のとおり、新しいメンバーを追加するときは、1つずつ構成し、正しく起動することを確認してから次を追加するのがベストプラクティスです。新しいメンバーの設定が正しくないと(ピアURLの誤りなど)、クラスターがクォーラムを失う可能性があるため、この段階的な方法は非常に重要です。ほかの既存メンバーから新しいメンバーに到達できなくても、新しいメンバーはクォーラムの計算に含まれるため、クォーラムを失うことになります。接続の問題や運用上の問題でも、クォーラムを失う可能性があります。

この問題を防ぐため、etcdは-strict-reconfig-checkオプションを提供します。このオプションを指定すると、再構成後のクラスターのクォーラムに必要な数より起動済みメンバーが少なくなる再構成リクエストを、etcdは拒否します。

このオプションは、デフォルトで有効です。

17 - サポート対象プラットフォーム

主なアーキテクチャーとオペレーティングシステムに対するetcdのサポート

サポート階層

etcdはさまざまなプラットフォームで動作しますが、保証の内容はプラットフォームのサポート階層によって異なります。

  • 階層1:etcdメンテナー が完全にサポートします。機能テストと堅牢性テストを含むすべてのテストに合格することを保証します。
  • 階層2:統合テストとエンドツーエンドテストへの合格を保証しますが、機能テストや堅牢性テストに合格するとは限りません。
  • 階層3:ビルドできることを保証します。簡単なテストしか行っていない、またはテストしていない可能性があるため、不安定なものとして扱うことを推奨します。

現在のサポート状況

次の表は、現在サポートされているプラットフォームと、それぞれのetcdサポート階層を示します。

アーキテクチャー オペレーティングシステム サポート階層 メンテナー
AMD64 Linux 1 etcdメンテナー
ARM64 Linux 1 etcdメンテナー
AMD64 Darwin 3
ARM64 Darwin 3
AMD64 Windows 3
ppc64le Linux 3
s390x Linux 3

表にないプラットフォームはサポート対象外です。

新しいプラットフォームのサポート

新しいプラットフォームの「公式」メンテナーとしてetcdに貢献する場合、プラットフォームのサポートを継続して担うとともに、サポート階層に応じて次の要件を満たすetcdの継続的インテグレーション(CI)を構築しなければなりません。

etcdの継続的インテグレーション 階層1 階層2 階層3
ビルドの成功 ✓ ✓ ✓
単体テストの合格 ✓ ✓
統合テストとエンドツーエンドテストの合格 ✓ ✓
堅牢性テストの合格 ✓

ARM64向けの階層2のCIを構築した例は、etcd PR #12928 を参照してください。

サポート対象外のプラットフォーム

サポート対象外のプラットフォームでetcdサーバーを誤って実行することを防ぐため、環境変数ETCD_UNSUPPORTED_ARCHに対象アーキテクチャーが設定されていない限り、etcdは警告メッセージを表示して直ちに終了します。

警告

32ビットシステム etcdには、Goランタイムのバグによる32ビットシステムでの既知の問題があります。 詳しくは、Goのissue #599 とatomicパッケージのバグに関する注記 を参照してください。

18 - バージョン管理

etcdでサポートされるバージョン

このドキュメントでは、etcdプロジェクトでサポートされるバージョンについて説明します。

サービスのバージョン管理とサポート対象バージョン

etcdのバージョンは、セマンティックバージョニング の用語に従い、x.y.zと表記します。xはメジャーバージョン、yはマイナーバージョン、zはパッチバージョンです。新しいマイナーバージョンでは、APIに機能が追加される場合があります。

etcdプロジェクトでは、現行バージョンとその前のリリースのリリースブランチを保守します。たとえば、v3.5が現行バージョンの場合、v3.4もサポートされます。v3.6がリリースされると、v3.4のサポートは終了します。

セキュリティー修正を含む適用可能な修正は、重大度と実現可能性に応じて、これら2つのリリースブランチにバックポートされる場合があります。必要に応じて、これらのブランチからパッチリリースを作成します。

この判断は、プロジェクトのメンテナー が行います。

稼働中のetcdクラスターのバージョンは、etcdctlで確認できます。

etcdctl --endpoints=127.0.0.1:2379 endpoint status

APIのバージョン管理

v3 APIのレスポンスは、3.0.0リリース以降は変更されない想定ですが、新機能は今後も追加されます。

19 - データ破損

etcdのデータ破損とリカバリー

etcdには、メンバー間で状態が分岐することを防ぐため、データ破損の自動検出機能が組み込まれています。

注記

etcd 3.7では、以下のフィーチャーゲートと時間間隔フラグを使用してください。以前の実験的なデータ破損チェック用フラグは削除されています。設定オプション を参照してください。

データ破損検出の有効化

データ破損は、次の方法で検出できます。

  • 初期チェック。--feature-gates=InitialCorruptCheck=trueフラグで有効にします。
  • 次の定期チェック。
    • コンパクション済みリビジョンのハッシュ。--feature-gates=CompactHashCheck=trueフラグで有効にします。
    • 最新リビジョンのハッシュ。--corrupt-check-timeフラグで有効にします。

初期チェックは、etcdメンバーのブートストラップ中に実行します。 メンバーは自身の永続状態をほかのメンバーと比較し、不一致があれば終了します。

両方の定期チェックは、稼働中のクラスターでリーダーが実行します。 リーダーは自身の永続状態をほかのメンバーと比較し、不一致があればCORRUPT ALARMを発報します。 両チェックの目的は同じですが、性能と検出までの時間のバランスを取るため、両方を有効にする価値があります。

  • コンパクション済みリビジョンのハッシュチェック - 定期的なコンパクションが必要です。性能への負荷はごく小さく、遅いフォロワーにも対応します。
  • 最新リビジョンのハッシュチェック - 性能への負荷が高く、遅いフォロワーや頻繁なコンパクションには対応しません。

コンパクション済みリビジョンのハッシュチェック

--feature-gates=CompactHashCheck=trueフラグで有効にすると、1分ごとにチェックを実行します。 間隔は--compact-hash-check-timeフラグで調整できます。形式は1mが毎分、1hが毎時です。 このチェックはコンパクション処理を拡張し、クラスターメンバー間で比較できるチェックサムも計算します。 データベースの追加スキャンを行わないため負荷は非常に小さいものの、クラスターで定期的にコンパクションを行う必要があります。

最新リビジョンのハッシュチェック

--corrupt-check-timeフラグで有効にします。実行間隔の指定が必要で、形式は1mが毎分、1hが毎時です。 性能への負荷が高いため、数時間の間隔を推奨します。 チェックでは、指定リビジョンのetcdの全内容をスキャンしてチェックサムを計算する必要があります。

破損したメンバーのリストア

破損したメンバーをリストアする方法は3つあります。

  • メンバーの永続状態を消去する
  • メンバーを置き換える
  • クラスター全体をリストアする

破損したメンバーをリストアした後、CORRUPT ALARMを解除できます。

メンバーの永続状態の消去

次の手順でメンバーの状態を消去できます。

  1. etcdインスタンスを停止します。
  2. etcdのデータディレクトリをバックアップします。
  3. etcdのデータディレクトリからsnapサブディレクトリを移動します。
  4. --initial-cluster-state=existingを指定し、--initial-clusterにクラスターメンバーを列挙してetcdを起動します。

etcdメンバーは、リーダーから最新のスナップショットをダウンロードするはずです。

メンバーの置き換え

次の手順でメンバーを置き換えられます。

  1. etcdインスタンスを停止します。
  2. etcdのデータディレクトリをバックアップします。
  3. データディレクトリを削除します。
  4. etcdctl member removeを実行し、クラスターからメンバーを削除します。
  5. etcdctl member addを実行し、メンバーを再度追加します。
  6. --initial-cluster-state=existingを指定し、--initial-clusterにクラスターメンバーを列挙してetcdを起動します。

クラスター全体のリストア

現在のリーダーからスナップショットを保存し、すべてのメンバーにリストアすることで、クラスターをリストアできます。 リーダーに対してetcdctl snapshot saveを実行し、クラスターのリストア手順 に従ってください。