はじめに
本記事では、kubeadmなどを使って自前で構築した Kubernetesクラスター上で、ブラウザに警告が出ない HTTPSサービスをデプロイする方法を解説します。
使用するサービス/ツールは、Amazon Route 53、cert-manager、Let’s Encryptです。
HTTPSサービス自体は、自己署名のSSL/TLS証明書(いわゆる自己証明書)を作成すれば実現できます。しかしその場合、ブラウザでアクセスするとセキュリティ警告が表示されてしまいます。これを回避するには、公的な認証局から発行されたTLS証明書を利用する必要がありますが、証明書の発行や更新にはいくつかの手順があり、初見では分かりづらい部分も少なくありません。
また、TLS 証明書の発行や更新は頻繁に行う作業ではないため、時間が経つと手順や仕組みを忘れてしまいがちです。
そこで本記事では、Kubernetes上で信頼されたTLS証明書を用いてHTTPSサービスを公開するまでの一連の流れを、できるだけ漏れなく整理して解説します。
前提条件
- AWSのアカウントを持っていること
- AWSのRoute 53でドメイン作成とDNSサーバー設定をするため
- サーバー関連
- Kubernetesクラスターを構築済であること
- パブリックIPアドレスでインターネットからアクセス可能であること
- 443ポートが開放されていること
- Kuberenetes関連
- HelmとHelmfileが使えること
システム構成
今回やりたいことを実現するための構成を、一つの図にまとめてみました。
各コンポーネントの役割/用途は以下です。
- Route 53
- HTTPSサービスを提供するサーバーのパブリックIPに、ホスト名(FQDN)を紐付ける
- IAMユーザー
- KubernetesのリソースがRoute 53にアクセスする際に利用
- Kubernetesクラスター
- cert-manager
- TLS証明書を発行するために必要な情報を用意して、Let's Encryptに提出
- Let's Encryptから取得したTLS証明書を格納
- Ingress
- 取得したTLS証明書を使って、HTTPSサービスを提供
- cert-manager
- Let's Encrypt
- TLS証明書を発行、更新
やることの整理
システム構成をもとに、必要な作業を整理します。大きくは、AWS側の作業とKubernetes側の作業に分かれます。
| 項目 | 作業内容 | 作業環境 | 備考 |
|---|---|---|---|
| 1 | Route 53で、ドメインを登録 | AWS | ドメインが有効になるまで10分くらい待つ 今回は、 [mydomain].link というドメイン名とする |
| 2 | Route 53で、サーバーのホスト名を名前解決するAレコードを追加 | AWS | 今回は、app.[mydomain].link というホスト名とする |
| 3 | KubernetesからRoute 53にアクセスするためのIAMユーザーを作成 | AWS | ポリシーと認証情報(アクセスキー等)も作成する |
| 4 | cert-managerをインストール | Kubernetes | |
| 5 | IAMユーザーの認証情報を格納するSecretリソースを作成 | Kubernetes | cert-managerがRoute 53にアクセスするために使う |
| 6 | cert-managerのClusterIssuerで、Let's Encryptにアカウント登録 | Kubernetes | |
| 7 | cert-managerのCertificateで、Let's EncryptからTLS証明書を取得 | Kubernetes | 取得するのに2,3分掛かる |
| 8 | Ingress Controllerをインストール | Kubernetes | 本記事ではIngress NGINX Controllerを使う |
| 9 | Webサービスをデプロイ | Kubernetes | Ingress, Service, Deployment等のリソースを作成する |
上記の項目番号を、システム構成の図と重ね合わせると、以下のようになります。
以降、具体的な手順を記載します。
手順:AWSリソースの作成
1. Route 53:ドメインを登録
まずはAWSのRoute 53で、Webサービスを稼働させるサーバーにホスト名を付与するためのドメインを作成します。
マネージメントコンソールから登録する場合は、Route 53のメニューで「ダッシュボード」を選択し、「ドメインの登録」の下のテキストボックスから登録可能です。
「チェック」を押下すると、料金の確認や連絡先情報の入力ページに遷移し、必要な情報を入力すれば登録手続きが完了します。以降の説明では、上の図でグレーで塗りつぶしている部分を [mydomain] として扱います。なお、トップレベルドメイン(TLD)に link を選んだのは、年額5ドルと他のTLDより安かったためです。
ドメイン登録後の確認
ドメインの登録が完了すると、メールアドレスにその旨の内容が送信され、次のようなドメインが新たに追加されていることを確認します。おそらく10分くらい掛かると思うので、状態がすぐに変わらなくても焦らずにお待ちください。
ドメインの作成と同時に、パブリックホストゾーンが以下のように自動で作成され、タイプがNS(Name server)とSOA(Start of authority)のレコードが追加されます。
2. Route 53:サーバーのホスト名をAレコードで追加
続いて、自動作成されたパブリックホストゾーンに、Webサーバーで使うホスト名をAレコード(IPv4アドレスに紐付ける)で追加します。
マネージメントコンソールでは、レコード一覧のページで「レコードの作成」を押下後に表示されたページで、以下の赤枠部分にレコード名とサーバーのパブリックIPアドレスを入力します。今回レコード名(サーバーのホスト名は)は app.[mydomain].link とします。
なお、このAレコードは以下のExternalDNSを使えばKubernetesクラスター側から自動作成できます。
私はまだ検証できていませんが、AWSマネージメントコンソールでの手動作成を省き、マニフェストで管理したい場合には有力な選択肢です。
3. KubernetesからRoute 53にアクセスするためのIAMユーザーを作成
Let's EncryptにTLS証明書を発行してもらうには、KubernetesがRoute 53のホストゾーンへアクセスできる必要があります。そこで、IAMユーザーと認証情報(アクセスキーとシークレットキー)を作成します。具体的な作成手順はここでは割愛します。
さらに、ホストゾーンのアクセスに関するポリシーを以下のように作成して、IAMユーザーに紐付けます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "route53:GetChange",
"Resource": "arn:aws:route53:::change/*"
},
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets"
],
"Resource": "arn:aws:route53:::hostedzone/*",
"Condition": {
"ForAllValues:StringEquals": {
"route53:ChangeResourceRecordSetsRecordTypes": ["TXT"]
}
}
},
{
"Effect": "Allow",
"Action": "route53:ListHostedZonesByName",
"Resource": "*"
}
]
}
このJSONファイルは、2026/01/12時点でcert-managerの以下に記載されたサンプルをコピペしたものです。
手順:TLS証明書の取得(Kubernetesで実行)
AWS側の準備が出来たので、KubernetesでTLS証明書を取得する作業を進めていきます。
4. cert-managerをインストール
以下のHelmfileを用意して、Kuberenetesクラスターにcert-managerをインストールします。
repositories:
- name: jetstack
url: https://charts.jetstack.io
releases:
- name: cert-manager
namespace: cert-manager
createNamespace: true
chart: jetstack/cert-manager
version: v1.19.2
set:
- name: crds.enabled
value: true
以下がインストールコマンドです。
helmfile apply -f helmfile-cert-manager.yaml
cert-managerのカスタムリソースが追加されていることを確認します。
$ kubectl api-resources --api-group=cert-manager.io
NAME SHORTNAMES APIVERSION NAMESPACED KIND
certificaterequests cr,crs cert-manager.io/v1 true CertificateRequest
certificates cert,certs cert-manager.io/v1 true Certificate
clusterissuers ciss cert-manager.io/v1 false ClusterIssuer
issuers iss cert-manager.io/v1 true Issuer
$ kubectl api-resources --api-group=acme.cert-manager.io
NAME SHORTNAMES APIVERSION NAMESPACED KIND
challenges acme.cert-manager.io/v1 true Challenge
orders acme.cert-manager.io/v1 true Order
また、cert-managerのPodが起動していることを確認します。
$ kubectl get po -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-75bb65b7b9-xfg7w 1/1 Running 0 40h
cert-manager-cainjector-5cd89979d6-4gnn2 1/1 Running 0 40h
cert-manager-webhook-8fc5dcf5f-gklvl 1/1 Running 0 40h
5. IAMユーザーの認証情報を格納するSecretリソースを作成
cert-managerのカスタムリソースがRoute 53にアクセスするために使うSecretリソースを作成します。
apiVersion: v1
kind: Secret
metadata:
name: route53-credentials
namespace: cert-manager
type: Opaque
stringData:
access-key-id: AKI... # 手順3で作成したIAMユーザーのアクセスキーを入れる
secret-access-key: xxxx... # 手順3で作成したIAMユーザーのシークレットキーを入れる
リソースを作成します。
kubectl apply -f route53-credentials.secret.yaml
6. cert-managerのClusterIssuerで、Let's Encryptにアカウント登録
続いて、Let's Encryptにアカウント登録するためのClusterIssuerのマニフェストを以下のように用意します。
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-mail@mail.com # AWSのドメイン登録時(手順1)に記載したメールアドレスを記入
privateKeySecretRef:
name: letsencrypt-key
solvers:
- dns01:
route53:
region: ap-northeast-1
hostedZoneID: Z... # AWSのドメイン登録時に作成されたホストゾーンIDを記入
accessKeyIDSecretRef: # 手順5で作成したSecretおよびアクセスキー名を記入
name: route53-credentials
key: access-key-id
secretAccessKeySecretRef: # 手順5で作成したSecretおよびシークレットキー名を記入
name: route53-credentials
key: secret-access-key
※ TLS証明書を特定のnamespaceでしか使わない場合は、Issuerリソースで作成しても問題ないです。
hostedZoneIDは、マネージメントコンソールで該当ホストゾーンのページに遷移後、以下の箇所で確認可能です。
またAWS CLIでも、以下のコマンドで確認可能です。
$ aws route53 list-hosted-zones \
--query "HostedZones[].{Name:Name,Id:Id}" \
--output table
---------------------------------------------------------
| ListHostedZones |
+------------------------------------+------------------+
| Id | Name |
+------------------------------------+------------------+
| /hostedzone/Z.................... | [mydomain].link. |
+------------------------------------+------------------+
マニフェストに必要な情報を記入したら、以下のコマンドでClusterIssuerリソースを作成します。
kubectl apply -f clusterissuer.yaml
以下のように、作成リソースの状態が true になれば一応OKです。
$ kubectl get clusterissuers
NAME READY AGE
letsencrypt True 21h
ただ、ClusterIssuerマニフェストの route53 配下の情報が誤っていてもこの時点では true になり、次のCertificateリソースを作成する際に失敗してしまうため、注意が必要です。
7. cert-managerのCertificateで、Let's EncryptからTLS証明書を取得
TLS証明書をLet's Encryptから取得するために、以下のCertificateリソースのマニフェストを用意します。
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: cert
namespace: default
spec:
secretName: cert-tls # TLS証明書を格納するSecretリソース名を記載
issuerRef: # 手順6で作成したClusterIssuerの情報を記載
name: letsencrypt
kind: ClusterIssuer
dnsNames:
- app.[mydomain].link # 手順2でホストゾーンに追加したレコードを記載
Certificateリソースを作成します。
kubectl apply -f certificate.yaml
ここからTLS証明書が手元に来るまで数分ほど時間が掛かります。この間、Let’s EncryptがACMEのchallengeと呼ばれるテストで、クライアントに対してドメインの所有確認を実施します。Kubernetesでは、一時的にChallengeというリソースが作成されて、以下のように検証が行われているのを確認できます。
$ kubectl get challenge -A
NAMESPACE NAME STATE DOMAIN AGE
default test-cert-1-1529098958-885014632 valid app.[mydomain].link 89s
$ kubectl describe challenges.acme.cert-manager.io | tail -n 6
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Started 109s cert-manager-challenges Challenge scheduled for processing
Normal Presented 82s cert-manager-challenges Presented challenge using DNS-01 challenge mechanism
Normal DomainVerified 20s cert-manager-challenges Domain "app.[mydomain].link" verified with "DNS-01" validation
また、マネージメントコンソールを見ると、以下のように _acme-challenge.app.[mydomain].link という検証用のTXTレコードが一時的に作成されるのも確認できます。
検証が終わったらこれらのリソースは削除されて、TLS証明書が利用可能な状態になります。
$ kubectl get certificate -n default
NAME READY SECRET AGE
cert True cert-tls 21h
$ kubectl describe certificate -n default cert | grep -A 32 ^Status
Status:
Conditions:
Last Transition Time: 2026-01-11T08:17:32Z
Message: Certificate is up to date and has not expired
Observed Generation: 1
Reason: Ready
Status: True
Type: Ready
Not After: 2026-04-11T07:18:59Z
Not Before: 2026-01-11T07:19:00Z
Renewal Time: 2026-03-12T07:18:59Z
Revision: 1
Events: <none>
そして、TLS証明書が cert-tls のSecretリソースに作られているのも確認できます。
$ kubectl describe secrets -n default cert-tls | grep -A 8 ^Data
Data
====
tls.crt: 3606 bytes
tls.key: 1675 bytes
手順:Webサービスをデプロイ(Kubernetesで実行)
TLS証明書を取得できたので、この証明書を使うWebサービスをデプロイします。
8. Ingress Controllerをインストール
Webサービスを公開するための Ingress Controller をインストールします。本記事では、以下のHelmfileでIngress NGINX Controllerをインストールします。
Ingress NGINX Controllerは2026年3月以降メンテナンスされない予定のため、長期運用を想定する場合は代替コントローラーの利用をご検討ください。
https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/
repositories:
- name: ingress-nginx
url: https://kubernetes.github.io/ingress-nginx
releases:
- name: ingress-nginx
namespace: ingress-nginx
createNamespace: true
chart: ingress-nginx/ingress-nginx
version: 4.14.1
values:
- controller:
hostNetwork: true # ホストネットワークで443を使うために設定
hostPort:
enabled: true # 同上
service:
type: ClusterIP
以下のコマンドでデプロイします。
helmfile apply -f helmfile-ingress-nginx.yaml
関連リソースがデプロイ済であることを確認します。
$ kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller ClusterIP 10.108.52.210 <none> 80/TCP,443/TCP 42h
ingress-nginx-controller-admission ClusterIP 10.104.30.201 <none> 443/TCP 42h
$ kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-controller-6d796b4c99-7kfdx 1/1 Running 0 42h
$ kubectl get ingressclasses
NAME CONTROLLER PARAMETERS AGE
nginx k8s.io/ingress-nginx <none> 42h
9. Webサービスをデプロイ
ようやくTLS証明書を使ったWebサービスがデプロイ可能な状態になりました。
はじめに、テスト用のWebサービスをDeploymentとServiceリソースでデプロイします。
kubectl create deployment https-demo-nginx \
-n default \
--image=nginx:1.25 \
--replicas=1
kubectl expose deployment https-demo-nginx \
-n default \
--port=80 \
--target-port=80
続いて、TLS証明書とホスト名を指定したIngressリソースのマニフェストを用意します。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: https-demo-nginx
namespace: default
labels:
app: https-demo
spec:
ingressClassName: nginx
tls:
- hosts:
- app.[mydomain].link # 手順2でホストゾーンに追加したホスト名を指定
secretName: cert-tls # 手順6でCertmanagerが生成したTLS証明書のSecretを参照
rules:
- host: app.[mydomain].link # 手順2でホストゾーンに追加したホスト名を指定
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: https-demo-nginx
port:
number: 80
このマニフェストをもとに、Ingressリソースをデプロイします。
kubectl apply -f https-demo-nginx.ing.yaml
正常に動作していることを確認します。
$ kubectl get ing -n default
NAME CLASS HOSTS ADDRESS PORTS AGE
https-demo-nginx nginx app.[mydomain].link 10.108.52.210 80, 443 5m19s
$ kubectl describe ingress https-demo-nginx -n default
Name: https-demo-nginx
Labels: app=https-demo
Namespace: default
Address: 10.108.52.210
Ingress Class: nginx
Default backend: <default>
TLS:
cert-tls terminates app.[mydomain].link
Rules:
Host Path Backends
---- ---- --------
app.[mydomain].link
/ https-demo-nginx:80 (192.168.0.144:80)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 5m17s (x2 over 5m50s) nginx-ingress-controller Scheduled for sync
Webサービスに安全に接続できることの確認
Webブラウザで、https://app.[mydomain].link と入力すると、確かに以下のように警告なしで表示されることを確認できます。
また、curl コマンドでも、証明書エラーを無視する -k オプションを指定しなくてもアクセスできることを確認できます。
$ curl https://app.[mydomain].link 2>/dev/null | head -n 4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
道のりは長かったですが、目的を達成できました!









