はじめに
小ネタです。記事タイトルが長いのですが、これは本稿の内容を1行で説明したものになります。
そして OpenSearch に限らない一般的な話題としては
という優れた情報が既にあります。本稿では Terraform コードの例示や背景についての解説を与えることで、付加価値を与えんとするものになります。
課題感
OpenSearch を AWS 上のマネージドサービスとして扱う場合、OpenSearch ドメインを外部からのアクセスが可能なものとして構築するか、VPC 内に閉じたものとして構築するかの2択があります*1。後者を選ぶ場合 OpenSearch ドメインへのアクセスは対象の VPC 内からのみアクセスが可能になります
また OpenSearch ドメインには作成後標準で払い出されるエンドポイントとは別にカスタムエンドポイントを設定することができます。これは apex を含む任意ドメインを利用者の所望のものにすることができるものになります*2
さて OpenSearch は通常 HTTPS による通信が強制されており、これは標準のエンドポイント / カスタムエンドポイント 両方で該当します。標準のエンドポイントについては AWS がよしなに SSL 証明書を設定してくれますが、カスタムエンドポイントについてはユーザ側で SSL 証明書を用意しなくてはなりません。
手軽にこのあたりをやるには ACM で SSL 証明書を払い出そうという話になり、より手軽にやろうとすればカスタムエンドポイント設定値としてのドメインの存在確認を DNS 検証でやる*3 という段取りになるでしょう。
ただし ACM による DNS 検証は インターネット経由で名前解決が可能な DNS レコード に対してのみ対応可能です。VPC に閉じる構成とした OpenSearch ドメインに対し設定するカスタムエンドポイントで使うドメインをインターネットから名前解決可能とするメリットは無いでしょうから、これも VPC 内からのみ名前解決が可能な Route 53 の private hosted zone で設定することになります*4。
つまり カスタムエンドポイントとして使用するドメインはインターネットから名前解決不可 になり、これで何が困るかというと ACM による DNS 検証が不可 となります。
いままでの議論をまとめると以下のようになります。

スプリットビュー DNS
これを解決するには本稿題名に掲げたスプリットビュー DNS*5が有効でしょう。AWS においては Route 53 に関するドキュメントのうち こちら にあるとおり、Route 53 において private / public の両 hosted zone を組み合わせることで実現が可能です。すなわち
- example.com という public な hosted zone を Route 53 に追加する
- internal.example.com という private な hosted zone を Route 53 に追加する
これだけです。opensearch.internal.example.com というドメインを VPC 内に閉じた OpenSearch ドメインのカスタムエンドポイントとして使用することを考えると
opensearchというレコードで OpenSearch エンドポイントを向く CNAME レコードを private 側の Route 53 hosted zone(internal.example.comに対応)へ設定opensearch.internal.example.comというドメインに対し ACM 上で SSL 証明書発行2.の結果得られた DNS 検証用レコードを public 側の Route 53 hosted zone(opensearch.internal.example.comに対応)へ設定- ここまでに得られた ACM 上 SSL 証明書の ARN とドメイン名とを OpenSearch へカスタムエンドポイントとして設定
という手筈を踏めば、ACM で DNS 検証が可能な OpenSearch カスタムドメイン向けの SSL 証明書が手に入り、かつ VPC 内に限って OpenSearch ドメインにてカスタムドメインを使っての通信が可能、という状態にもってゆくことができます*6。

サンプルコード
上記構想を AWS マネジメントコンソールでやるのは少々骨です。実際に使用している Terraform コードに適宜修正を加えたサンプルコードを例示します
variable "hostzone" { type = object({ external = string internal = string }) default = { external = "example.com" internal = "internal.example.com" } } variable "custom_endpoint" { type = string nullable = true default = "opensearch.internal.example.com" } resource "aws_vpc" "main" { # 詳細略 } /* Route 53 関連 各 hosted zone 定義と OpenSearch カスタムエンドポイントとして使うドメインに対応する DNS レコードの設定を行う */ resource "aws_route53_zone" "external" { name = var.hostzone.external } resource "aws_route53_zone" "internal_split_dns" { name = var.hostzone.internal vpc { vpc_id = aws_vpc.main.id } } resource "aws_route53_record" "opensearch_custom_endpoint" { # 本稿の例では常時 true だが var.custom_endpoint が無い場合はレコード作成不要なので条件分岐できるようにしておく for_each = ( var.custom_endpoint != null ? { "main" = {} } : {} ) zone_id = aws_route53_zone.internal_split_dns.zone_id /* var.custom_endpoint の apex ドメインは aws_route53_zone.internal_split_dns で指される Route 53 ゾーンに一致する必要がある この理屈から当該 apex zone に設定すべき OpenSearch ドメイン用 DNS レコードの値は var.custom_endpoint から apex zone 名を差っ引けば得られる */ name = replace(var.custom_endpoint, aws_route53_zone.internal_split_dns.name, "") type = "CNAME" ttl = 60 records = [ aws_opensearch_domain.main.endpoint, ] } /* ここから ACM 関連 証明書払い出しと DNS 検証のための各種設定までを行う */ resource "aws_acm_certificate" "internal_split_dns" { domain_name = var.custom_endpoint validation_method = "DNS" } resource "aws_route53_record" "internal_split_dns" { for_each = { for dvo in aws_acm_certificate.internal_split_dns.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type } } zone_id = aws_route53_zone.external.id allow_overwrite = true name = each.value.name records = [each.value.record] ttl = 600 # 適当に変更可 type = each.value.type } resource "aws_acm_certificate_validation" "internal_split_dns" { certificate_arn = aws_acm_certificate.internal_split_dns.arn validation_record_fqdns = [for record in aws_route53_record.internal_split_dns : record.fqdn] timeouts { create = "15m" } } resource "aws_opensearch_domain" "main" { /* カスタムエンドポイント関連以外の詳細は省略 実際に OpenSearch ドメインを構築するには他にも必須項目がある See: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/opensearch_domain */ domain_endpoint_options { enforce_https = true custom_endpoint_enabled = var.custom_endpoint == null ? false : true custom_endpoint = var.custom_endpoint custom_endpoint_certificate_arn = aws_acm_certificate.internal_split_dns.arn } encrypt_at_rest { enabled = true } node_to_node_encryption { enabled = true } }
おわりに
簡単ではありますが OpenSearch 構成の tips について解説する項目となりました。カスタムエンドポイントを使わない場合、OpenSearch のデフォルトでは
https://search-<ドメイン名>-<ランダム文字列>.<リージョン>.es.amazonaws.com
https://vpc-search-<ドメイン名>-<ランダム文字列>.<リージョン>.es.amazonaws.com
となります。これが本稿の例でいえば
https://opensearch.internal.example.com
として扱えるようになり、シンプルなエンドポイントで OpenSearch を使うことが可能になります。
用途その他の事情で OpenSearch デフォルトのものではないエンドポイントを設定したくなり、またその対象となる OpenSearch ドメインが VPC 内からのみ利用可能なものであった場合に、本稿の内容がお役に立てば幸いです。だいぶニッチな話題ではありますが、そういった需要は決してゼロではないと考えています。
MNTSQ 株式会社 SRE 秋本
参考
文中で示した引用以外のものとして、本稿の話題に取り組む前の調査時に 【Route53】スプリットビューDNSの名前解決順序を整理してみた | DevelopersIO を拝読しました。
*1:https://docs.aws.amazon.com/opensearch-service/latest/developerguide/vpc.html
*2:https://docs.aws.amazon.com/opensearch-service/latest/developerguide/customendpoint.html
*3:https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html
*4:OpenSearch / ACM / VPC とお膳立てを AWS 前提として進めてきたので、いきなり DNS 関係も Route 53 を使うこととしてもさして矛盾はしないと思っています
*5:本稿を書く上でこの定義が気になったので調べたところ https://datatracker.ietf.org/doc/html/rfc8499 や https://datatracker.ietf.org/doc/html/rfc6950/ が有用そうでした。本稿を読む上でこの内容を理解する必要は全くありません。これはほぼ私的なメモです
*6:直下の図中 xN の表記は https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html に拠ります
*7:https://docs.aws.amazon.com/opensearch-service/latest/developerguide/createupdatedomains.html