🏛️

HashiCorp Consulを活用してメンテナンス効率化

に公開

はじめに

三菱UFJインフォメーションテクノロジー株式会社の鵜飼健史と申します。
Zennでのテックブログは初投稿となります。
よろしくお願いいたします。

最近の分散系システムにおけるサービスディスカバリー製品について記事を書きました。

今回、プラットフォームに依存しない形で運用できる「HashiCorp Consul」を使った実際の運用例を紹介します。

本記事の趣旨

  • HashiCorp ConsulとDNSを組み合わせてサービスディスカバリとして利用できる
  • メンテナンス作業はHashiCorp Consulを活用して一括コマンド投入
  • EOS対応もHashiCorp Consulを使えば無停止で可能なものもある

利用したバージョンなど

  • HashiCorp Consul 1.20.2
  • RedHat Enterprise Linux 9.1

サービスメッシュ、サービスディスカバリとは

サービスメッシュとは「アプリケーション内のサービス間通信を管理するための仕組み」です。
サービスディスカバリーは「アプリケーション内のサービスの位置情報を見つける仕組み」であり、サービスメッシュの一部となります。
マイクロサービスの考え方が浸透していくに従って「アプリケーション内でのサービス」の構造が非常に複雑になっています。

2010年くらいまではAPサーバとDBサーバが決まった場所に配置されていることが多かったのですが、最近ではAPの中だけでもAPIやWeb層が分割されたり、ドメインごとにサーバ自体も分割されたりしています。

サービスメッシュの例

そうすると、それぞれのサービスがどのようにして通信相手となるサービスを見つけるか、といったサービスディスカバリに関して運用管理の負荷が上がることが課題になります。

それを解決するための仕組みとして、サービスメッシュやサービスディスカバリの製品があります。

HashiCorp Consulとは

本記事で紹介するHashiCorp Consul(以下、Consul)は、HashiCorp社が主に開発するOSSのサービスディスカバリ、サービスメッシュ製品となります。
https://www.consul.io

Consulの特徴としては以下があげられます。

  • クラウドや基盤に依存せず、マルチプラットフォームで稼働
  • 動的なロードバランスや障害時の切り替えが可能
  • Observabilityの仕組みとも連携でき、メトリクスを取得可能

Consulの基本構成

Consulは3台以上のserverと配下に属する任意の台数のclientから構成されます。

clientは管理対象となるサーバに導入され、そのサーバでどのようなサービスが稼働しているかを管理してserverに報告します。
serverclientが保持ししているサービス情報を収集し、clientからの問い合わせに回答します。
また、高可用性担保のため3台以上のサーバで稼働させることで、生存確認や管理情報の同期を行います。

構成図

Consulの導入

Server

パッケージをインストールします。

command
$ dnf install -y consul

設定ファイルを以下の通り書き換えます。

/etc/consul.d/consul.json@server
{
    "datacenter": "consul-sample",
    "data_dir": "/opt/consul",
    "client_addr": "0.0.0.0",
    "bind_addr": "10.10.10.1",
    "start_join": ["10.10.10.1", "10.10.10.2", "10.10.10.3"],
    "enable_script_checks": true,
    "domain": "mesh.sample.com",
    "alt_domain": "consul",
    "disable_remote_exec": false,
    "connect": {
      "enabled": true
    },
    "ports": {
      "https": 8501,
      "grpc": 8502,
      "grpc_tls": 8503
    },
    "disable_update_check": true,
    "enable_central_service_config": true,
    "tls": {
      "defaults": {
        "cert_file": "/etc/pki/consul/tls.crt",
        "key_file": "/etc/pki/consul/tls.key",
        "tls_cipher_suites": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
      }
    },
    "server": true
  }

datacenterはConsulが管理する単位を定義する設定値です。
管理単位で任意の文字を設定します。

    "datacenter": "consul-sample",

data_dirはConsulがデータの永続化に利用するKey-Valueストアの配置場所を指定します。

    "data_dir": "/opt/consul",

client_addrはConsulとして通信をする送信元IPアドレスを記載します。
ここでは特に制御しないため、0.0.0.0を設定します。

    "client_addr": "0.0.0.0",

bind_addrはConsulとして通信をする受信IPアドレスを記載します。
ここでは自分自身のIPアドレスを設定します。

    "bind_addr": "10.10.10.1",

start_joinはConsul起動時のserverのIPアドレスをリストで指定します。
3台以上の設定が必須になります。

    "start_join": ["10.10.10.1", "10.10.10.2", "10.10.10.3"],

enable_script_checksはスクリプトを使ってサービス状態監視を行う際に設定します。

    "enable_script_checks": true,

domainはConsulが管理するサービス群の親ドメインをしています。

    "domain": "mesh.sample.com",

alt_domainはドメイン名の別名を登録します。
こちらのドメインでもDNSを引けるようになります。

    "alt_domain": "consul",

disable_remote_execはリモートでのコマンド実行を禁止する設定です。
今回こちらを利用するのでfalseで設定します。

    "disable_remote_exec": false,

connectはサービスメッシュの定義を設定します。
今回はサービスディスカバリのみのため、以下のみとします。

    "connect": {
      "enabled": true
    },

portsにて各通信のポートを指定します。
デフォルト設定でかまいません。

    "ports": {
      "https": 8501,
      "grpc": 8502,
      "grpc_tls": 8503
    },

tlsにて証明書や鍵の場所、CipherSuitesなどを指定します。

    "tls": {
      "defaults": {
        "cert_file": "/etc/pki/consul/tls.crt",
        "key_file": "/etc/pki/consul/tls.key",
        "tls_cipher_suites": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
      }
    },

serverにてこのNodeがserverかどうかを判定します。
この設定ではserverのためtrueを指定します。

    "server": true

設定出来たら、Consulを起動します。ついでに自動起動設定もしておきましょう。

command
$ systemctl start consul
$ systemctl enable consul

これでserverは完成です。これを3サーバ分実施します。

Client

続いてclientも同様にインストールし、設定ファイルを以下として起動します。

/etc/consul.d/consul.json@client
{
    "datacenter": "consul-sample",
    "data_dir": "/opt/consul",
    "client_addr": "0.0.0.0",
    "bind_addr": "10.10.10.11",
    "start_join": ["10.10.10.1", "10.10.10.2", "10.10.10.3"],
    "enable_script_checks": true,
    "domain": "mesh.sample.com",
    "alt_domain": "consul",
    "disable_remote_exec": false,
    "connect": {
      "enabled": true
    },
    "ports": {
      "https": 8501,
      "grpc": 8502,
      "grpc_tls": 8503
    },
    "server": false
}

clientのため、以下の設定がserverとの差分となります。

    "server": false

これで準備は完了しました。

稼働確認

ちゃんとserverclientが稼働しているか確認するコマンドがあります。

command
$ consul members
Node     Address            Status  Type    Build   Protocol  DC             Partition  Segment
server1  10.10.10.1:8301    alive   server  1.20.2  2         consul-sample  default    <all>
server2  10.10.10.2:8301    alive   server  1.20.2  2         consul-sample  default    <all>
server3  10.10.10.3:8301    alive   server  1.20.2  2         consul-sample  default    <all>
client1  10.10.10.11:8301   alive   client  1.20.2  2         consul-sample  default    <default>
client2  10.10.10.12:8301   alive   client  1.20.2  2         consul-sample  default    <default>
client3  10.10.10.13:8301   alive   client  1.20.2  2         consul-sample  default    <default>

サーバのリストが出てくれば成功です。
どれがserverclientかも一目でわかります。

Consulを使ったサービスディスカバリ

サービス設定

サービスをclientに登録する際は/etc/consul.d/フォルダに任意の設定ファイルを新規作成します。
ここでは一例としてNginxのサービスを登録する例となります。

/etc/consul.d/service_nginx.json
{
    "service": {
        "address": "10.10.10.11",
        "enable_tag_override": false,
        "id": "nginx",
        "name": "nginx",
        "port": 443
    }
}

サービス名やIPアドレス、ポートなどを定義します。
ファイルを追加した場合は以下コマンドで設定を反映します。

command
$ consul reload

サービスアクセス

ConsulにはDNSの機能もあるため、digコマンドで8600ポートに対して確認すると以下のように応答してくれます。

command
$ dig -p 8600 nginx.serivce.consul
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el8 <<>> nginx.service.consul
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42302
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
 
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;nginx.service.consul.     IN      A
 
;; ANSWER SECTION:
nginx.service.consul. 0    IN      A       10.10.10.11

;; Query time: 5 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: 木  2月 13 10:20:10 JST 2025
;; MSG SIZE  rcvd: 70

これは同じdatacenter内のどのclientに問い合わせても同じ答えが返ってくるようになります。
また、同じサービス名のclientが2台以上ある場合は、DNSのリストに2台とも含まれます。

よって、Webサーバ製品とConsulを連携させることで先ほど定義したサービスファイルで動的にサービスの増強や追加、切り替えができるようになります。

また、トラブルがあった場合に自動切り離しをしたい、ということであればサービスファイルにcheckの定義を追加して任意の確認コマンドを登録できます。

メンテナンスでの一括コマンド投入

ConsulはCLIも用意されており、consul exec機能を使うことで配下サーバに対してまとめてコマンドを投入できます。

これを使うことで、全サーバの状態確認や、設定値の横断確認、セキュリティアップデートの適用などが一括でできるようになります。

例えば、javaバージョン確認がしたい場合は以下コマンドを実行します。

command
$ consul exec "java --version"
==> server1: finished with exit code 0
    server1: openjdk 11.0.16 2022-07-19 LTS
    server1: OpenJDK Runtime Environment (Red_Hat-11.0.16.0.8-1.el8_6) (build 11.0.16+8-LTS)
    server1: OpenJDK 64-Bit Server VM (Red_Hat-11.0.16.0.8-1.el8_6) (build 11.0.16+8-LTS, mixed mode, sharing)
==> server2: finished with exit code 127
    server2: /bin/bash: java: コマンドが見つかりません
==> server3: finished with exit code 0
    server3: openjdk 17.0.9 2023-10-17 LTS
    server3: OpenJDK Runtime Environment (Red_Hat-17.0.9.0.9-1) (build 17.0.9+9-LTS)
    server3: OpenJDK 64-Bit Server VM (Red_Hat-17.0.9.0.9-1) (build 17.0.9+9-LTS, mixed mode, sharing)

上記結果からそれぞれserver1はJava11、server2はインストールされておらず、server3はJava17であることが分かります。

運用をしていると「全サーバには投入したくないがある程度まとめてやりたい」という要望も出てくると思います。
弊社のシステムでは以下のノードタグをつけて運用しています。

/etc/consul.d/consul.json
...
    "node_meta": {
      "env": "dev",
      "os": "Red Hat Enterprise Linux release 9.1 (Plow)",
      "lang": "ja_JP.UTF-8",
      "parity": "odd"
    },
...

  • 提供用サービスかどうか
  • OSバージョン
  • デフォルト言語設定
  • サーバ号機の偶奇

node_mataを使って順次偶数号機→奇数号機と半分ずつバージョンアップしたい場合は以下のコマンドをそれぞれタイミングをずらして実行します。

command
$ consul exec -node="$(consul catalog nodes -node-meta="parity=even" | cut -d ' ' -f 1 | sed '1d' | head -c -1 | tr '\n' '|')" "dnf upgrade -y nginx
$ consul exec -node="$(consul catalog nodes -node-meta="parity=odd" | cut -d ' ' -f 1 | sed '1d' | head -c -1 | tr '\n' '|')" "dnf upgrade -y nginx

他にもsshdが落ちてしまって、動かなくなったサーバを復旧するためのコマンドを投入したり、自動構築スクリプトを配布して一括インストールをしたりなど、メンテナンスで活躍します。

Consulを利用したEOS対応

RHEL8にて運用しているサービスをRHEL9にバージョンアップするといったEOS対応が数年に一度発生します。

従来のやり方だと、大規模な案件を計画して、今のサーバ構成をそのままRHEL9で作り直してテストを実施、サービス停止時間を設けてデータ移行等を行った上で切り替えという対応がほとんどでした。

これをConsulを使ってサービス単位でEOSができるようになります。

例えば、Nginx、JakartaEE Server、Databaseの3つで構成されるアプリケーションがあった場合、

Nginxのみや、JakartaEE Serverのみ、といった単位でEOS対応を進めることが可能です。

実施イメージ

RHEL9サーバを構築して、そこにConsulをインストールします。
Nginxもそこにインストールして、稼働に問題ないかテストを行います。
問題なければ、サービスファイルを作成・登録するとRHEL8とRHEL9どちらにも通信が来るようになります。

もし問題があれば以下コマンドですぐにメンテナンスモードとして切り離すこともできます。

command
$ consul maint -service nginx -enable

メンテナンスモードを有効化するという意味なので、enableにするとサービスとしては無効になります。
よく間違えるので、注意が必要です。

両現用で稼働させておいて、RHEL8とRHEL9の稼働に遜色がなければRHEL8側をメンテナンスモードにしましょう。

しばらく期間をおいて問題なければRHEL8サーバは廃止して対応完了になります。

サービスごとの自動テストが組み込まれていれば、EOS対応も非常に簡単にできるようになります。

まとめ

  • HashiCorp Consulを使ってサービスファイルを設定することで簡単にサービスディスカバリを実現できます
  • 動的に設定ファイルを切り替えたり、CLIで制御したりして運用が簡単になります
  • メンテナンス作業やEOS対応にも使えます

これら以外にもKubernetesとの連携など、Consulには様々な機能があり、弊社でもまだまだ活用しきれていないため、引き続き運用管理の改善に努めようと思います。

Discussion