1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オンプレKubernetesで信頼済みTLS証明書を導入してHTTPS公開する手順

Last updated at Posted at 2026-01-12

はじめに

本記事では、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が使えること

システム構成

今回やりたいことを実現するための構成を、一つの図にまとめてみました。

オンプレk8sでHTTPS用のTLS証明書が使えるようにする-ページ2.png

各コンポーネントの役割/用途は以下です。

  • Route 53
    • HTTPSサービスを提供するサーバーのパブリックIPに、ホスト名(FQDN)を紐付ける
  • IAMユーザー
    • KubernetesのリソースがRoute 53にアクセスする際に利用
  • Kubernetesクラスター
    • cert-manager
      • TLS証明書を発行するために必要な情報を用意して、Let's Encryptに提出
      • Let's Encryptから取得したTLS証明書を格納
    • Ingress
      • 取得したTLS証明書を使って、HTTPSサービスを提供
  • 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等のリソースを作成する

上記の項目番号を、システム構成の図と重ね合わせると、以下のようになります。

オンプレk8sでHTTPS用のTLS証明書が使えるようにする-ページ3.png

以降、具体的な手順を記載します。

手順:AWSリソースの作成

1. Route 53:ドメインを登録

まずはAWSのRoute 53で、Webサービスを稼働させるサーバーにホスト名を付与するためのドメインを作成します。
マネージメントコンソールから登録する場合は、Route 53のメニューで「ダッシュボード」を選択し、「ドメインの登録」の下のテキストボックスから登録可能です。

1_ドメイン登録1_mask.png

「チェック」を押下すると、料金の確認や連絡先情報の入力ページに遷移し、必要な情報を入力すれば登録手続きが完了します。以降の説明では、上の図でグレーで塗りつぶしている部分を [mydomain] として扱います。なお、トップレベルドメイン(TLD)に link を選んだのは、年額5ドルと他のTLDより安かったためです。

ドメイン登録後の確認

ドメインの登録が完了すると、メールアドレスにその旨の内容が送信され、次のようなドメインが新たに追加されていることを確認します。おそらく10分くらい掛かると思うので、状態がすぐに変わらなくても焦らずにお待ちください。

1_ドメイン登録4_mask.png

ドメインの作成と同時に、パブリックホストゾーンが以下のように自動で作成され、タイプがNS(Name server)とSOA(Start of authority)のレコードが追加されます。

1_ホストゾーン_mask.png

2. Route 53:サーバーのホスト名をAレコードで追加

続いて、自動作成されたパブリックホストゾーンに、Webサーバーで使うホスト名をAレコード(IPv4アドレスに紐付ける)で追加します。

マネージメントコンソールでは、レコード一覧のページで「レコードの作成」を押下後に表示されたページで、以下の赤枠部分にレコード名とサーバーのパブリックIPアドレスを入力します。今回レコード名(サーバーのホスト名は)は app.[mydomain].link とします。

2_レコード追加_mask.png

なお、この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をインストールします。

helmfile-cert-manager.yaml
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リソースを作成します。

route53-credentials.secret.yaml
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のマニフェストを以下のように用意します。

clusterissuer.yaml
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は、マネージメントコンソールで該当ホストゾーンのページに遷移後、以下の箇所で確認可能です。

6_ホストゾーンID_mask.png

また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リソースのマニフェストを用意します。

certificate.yaml
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レコードが一時的に作成されるのも確認できます。

7_challenge_mask.png

検証が終わったらこれらのリソースは削除されて、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/

helmfile-ingress-nginx.yaml
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リソースのマニフェストを用意します。

https-demo-nginx.ing.yaml
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 と入力すると、確かに以下のように警告なしで表示されることを確認できます。

10_安全な接続1.png

10_安全な接続2.png

また、curl コマンドでも、証明書エラーを無視する -k オプションを指定しなくてもアクセスできることを確認できます。

$ curl https://app.[mydomain].link 2>/dev/null | head -n 4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

道のりは長かったですが、目的を達成できました!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?