---
title: wildwindstudio.com 証明書の自動更新 + Tencent Cloud CLB 再バインド
url: https://doc.liz6.com/ja/devops/runbooks/wildwind-clb-cert-renewal
locale: ja
area: devops
tags:
- devops
- runbooks
date: 2026-06-30
modified: 2026-07-16
description: 'ホスト: bj-middleware-1（root、TencentOS、Tencent Cloud 北京）'
---

# wildwindstudio.com 証明書の自動更新 + Tencent Cloud CLB 再バインド

> ホスト：**bj-middleware-1**（root、TencentOS、Tencent Cloud 北京）

---

## 1. 全体フロー

<svg viewBox="0 0 720 420" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="証明書の自動更新全体フロー: 定期トリガーから Tencent Cloud CLB 再バインドまで">
  <defs>
    <marker id="wcah1" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="420" fill="#ffffff"/>
  <text x="360" y="24" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">全体フロー: 定期トリガー → 証明書ごとの更新 → 成功時のみ CLB 再バインド</text>

  <rect x="110" y="40" width="500" height="32" rx="6" fill="#e2e8f0"/>
  <text x="360" y="60" text-anchor="middle" font-size="12" fill="#334155">/etc/crontab · 毎月1日・15日 02:00 にトリガー</text>

  <line x1="360" y1="72" x2="360" y2="83" stroke="#475569" stroke-width="1.6" marker-end="url(#wcah1)"/>

  <rect x="60" y="84" width="600" height="62" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="104" font-size="12.5" font-weight="700" fill="#3730a3">renew-and-update-clb.sh · オーケストレーションスクリプト(中核)</text>
  <text x="76" y="121" font-size="10.5" fill="#4f46e5">各証明書に対して certbot renew --no-random-sleep-on-renew を実行</text>
  <text x="76" y="137" font-size="10.5" fill="#4f46e5">--dns-tencentcloud-propagation-seconds 30 --deploy-hook …; 各処理完了後 sleep 60（負荷分散、レートリミット回避）</text>

  <line x1="360" y1="146" x2="360" y2="159" stroke="#475569" stroke-width="1.6" marker-end="url(#wcah1)"/>

  <rect x="60" y="160" width="600" height="48" rx="8" fill="#e0e7ff"/>
  <text x="76" y="178" font-size="12" font-weight="700" fill="#3730a3">certbot(Let's Encrypt · DNS-01 検証)</text>
  <text x="76" y="196" font-size="10.5" fill="#4338ca">certbot-dns-tencentcloud プラグインを使用して DNSPod に _acme-challenge TXT レコードを書き込み · 認証情報は tencentcloud.ini</text>

  <line x1="360" y1="208" x2="360" y2="221" stroke="#475569" stroke-width="1.6" marker-end="url(#wcah1)"/>
  <text x="380" y="218" font-size="10.5" font-weight="600" fill="#c2410c">証明書が「実際に更新された場合のみ」呼び出し →</text>

  <rect x="60" y="222" width="600" height="62" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="76" y="242" font-size="12.5" font-weight="700" fill="#115e59">clb-deploy-hook.sh(deploy-hook、更新成功時のみ呼び出される)</text>
  <text x="76" y="260" font-size="10.5" fill="#0f766e">① tccli ssl UploadCertificate — 新証明書を Tencent Cloud SSL にアップロードし、CertId を取得</text>
  <text x="76" y="276" font-size="10.5" fill="#0f766e">② tccli clb ModifyListener — CertId を対応する CLB listener に再バインド · 認証情報は tccli.env(600)</text>

  <line x1="360" y1="284" x2="360" y2="297" stroke="#475569" stroke-width="1.6" marker-end="url(#wcah1)"/>

  <rect x="110" y="298" width="500" height="34" rx="6" fill="#dcfce7" stroke="#4ade80"/>
  <text x="360" y="320" text-anchor="middle" font-size="12" font-weight="700" fill="#166534">Tencent Cloud CLB listener が新証明書に更新される</text>

  <rect x="60" y="352" width="600" height="54" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="374" font-size="12.5" fill="#3730a3">重要な設計: 証明書ごとの直列実行 + sleep 60 で Tencent Cloud DNS API のレートリミットを根本から回避;</text>
  <text x="76" y="392" font-size="12.5" fill="#3730a3">deploy-hook は実際に更新された場合のみ再バインドを実行 —— 失敗しても誤って更新されないが、更新自体の監視が必要。</text>
</svg>

ログは `/var/log/certbot-renewal.log` に一元化されます。certbot 自身のデバッグログは `/var/log/letsencrypt/` にあります。

### 重要な設計ポイント
- **証明書ごとの直列実行 + sleep 60**：根本的な目的は Tencent Cloud DNS API のレートリミットを回避することです（第4節の根本原因参照）。`certbot renew` で一括実行する旧方式に戻さないでください。
- **deploy-hook モデル**：証明書が「実際に更新された場合のみ」アップロード/再バインドがトリガーされ、同じ証明書の無意味な再アップロードを避けます。これは旧スクリプト（無条件で再送信）よりも正しいアプローチですが、同時に——**更新に失敗した場合、CLB は更新されない**ことを意味するため、更新自体の健全性監視が必要です（第6節参照）。

---

## 2. 証明書 ↔ CLB マッピング

リージョンは固定 `ap-beijing`、現在はすべて**単一ドメイン**証明書です：

| 証明書（certbot cert-name）            | ドメイン                              | CLB         | Listener      |
|--------------------------------------|-----------------------------------|-------------|---------------|
| `jsvc.wildwindstudio.com-0001`       | jsvc.wildwindstudio.com           | lb-fkcz6lot | lbl-ehxkll9d  |
| `passport.wildwindstudio.com`        | passport.wildwindstudio.com       | lb-b6j4kn47 | lbl-0jzb58rx  |
| `cn-reserve-api.wildwindstudio.com`  | cn-reserve-api.wildwindstudio.com | lb-mm70ra69 | lbl-naeanxnv  |

> マッピングは `clb-deploy-hook.sh` の `case` ブランチで管理されています。証明書の新規追加/変更時は、ここと `renew-and-update-clb.sh` 上部の `CERTS=(...)` リストを修正してください。
>
> `jsvc` 証明書の cert-name に `-0001` サフィックスが付いているのは歴史的経緯です（同名の3ドメイン SAN 系譜が `jsvc.wildwindstudio.com` という名前を占有していたため）。機能には影響ありません。
> **resflow.wildwindstudio.com は退役済み**（DNS なし、オンラインサービスなし）であり、その旧 CLB `lb-mm70ra69` は現在 cn-reserve-api で再利用されています。

---

## 3. ファイル一覧

| パス                                      | 説明                                              | 権限 |
|-------------------------------------------|---------------------------------------------------|------|
| `/etc/crontab`                            | 定期エントリ（平文キーを含まない）                 | 644  |
| `/usr/local/bin/renew-and-update-clb.sh`  | 更新オーケストレーションスクリプト                 | 755  |
| `/usr/local/bin/clb-deploy-hook.sh`       | certbot deploy-hook: アップロード + CLB 再バインド | 755  |
| `/usr/local/bin/clb-bind-one.sh`          | 手動単一証明書再バインドツール（緊急用）           | 755  |
| `/etc/letsencrypt/tccli.env`              | tccli 認証情報（アップロード/再バインド用）          | 600  |
| `/etc/letsencrypt/tencentcloud.ini`       | certbot DNS プラグイン認証情報（DNS-01 TXT 書き込み用） | 600  |
| `/var/log/certbot-renewal.log`            | 更新チェーンログ                                   | -    |

> 注意：DNS プラグインと tccli は**異なる2セットの Tencent Cloud API Key**を使用しています。前者は DNSPod 権限のみ必要、後者は SSL + CLB 権限が必要です。

---

## 4. トラブルシューティング記録（2026-06-24）

### 現象
- CLB `lb-fkcz6lot`（jsvc.wildwindstudio.com）の証明書が期限切れ、「更新スクリプトが反応せず」、オンラインでは期限切れの証明書が発行されていました。
- 同一バッチの passport、jsvc はすべて 2026-06-24 に期限切れとなりました。

### 調査手順と発見
1. **エントリポイントの特定**：`/etc/crontab` に毎月1日/15日 02:00 に `renew-and-update-clb.sh` を実行する設定があり、ログは `/var/log/certbot-renewal.log` に出力されます。
2. **ログの確認**：06-15 の `certbot renew` で jsvc/resflow に対して以下が報告されました。
   ```
   failed to determine base domain, please report to dev.
   Tried: ['wildwindstudio.com', 'jsvc.wildwindstudio.com']
   ```
   つまり、certbot の更新は**常に失敗していました**。
3. **プラグインソースコードの読取** `certbot_dns_tencentcloud/certbot_tencentcloud_plugins.py` の `determine_base_domain()`：
   ```python
   try:
       resp = client.describe_record_list(dt)
   except APIException as _:   # ← 任意の API 例外を「このドメインを所有していない」として扱う
       continue
   ...
   raise PluginError("failed to determine base domain ...")
   ```
   **任意の API 例外（レートリミット / QPS / 一時的エラー）がすべてキャッチされ**、最終的にこの誤解を招く "base domain" エラーがスローされます。
4. **対照確認**：cn-reserve-api は同じアカウント、同じ `tencentcloud.ini`、同じルートドメインですが、06-16 に**個別に**発行した場合は成功しました。違いは「個別実行 vs 一括連続実行」のみです。
5. **staging / 本番発行の再現**：単一 `certbot renew --cert-name jsvc...` を実行すると base-domain のステップをスキップして更新に成功 → **一括連続実行がレートリミットをトリガーし、プラグインによって base-domain エラーとして隠蔽されていたことが確定**。
6. **旧スクリプトが問題を隠蔽**：06-16 以前の旧スクリプトロジックは「certbot の成否に関わらず、無条件で既存証明書を再アップロードして CLB に再バインド」でした。そのため、04〜06月は毎回「✓ 再バインド成功」と表示されていましたが、バインドされていたのは03-26に発行され06-24に期限切れとなる**古い証明書**であり、「更新が正常に動作している」という偽りの状態を演出していました。06-16 に deploy-hook モデルに変更した後、更新失敗時に再バインドが行われなくなったため、問題が表面化しました。

### 根本原因
予約（cn-reserve）サービスで**2つのドメインが新規追加**された後、1回の更新バッチで処理するドメイン数が増加し、短時間で Tencent Cloud DNS API への密集呼び出しが発生 → レートリミットをトリガー → certbot DNS 検証で結果が取得できず → 更新失敗 → 証明書が期限切れになっても自動更新されなかった。

---

## 5. 今回の対応

### 緊急更新
- jsvc / passport を個別（直列、隔離）で手動更新し、レートリミットを回避；`clb-bind-one.sh` を使用して対応する CLB にアップロードおよび再バインド。
- 結果：lb-fkcz6lot、lb-b6j4kn47 のオンライン証明書がすべて更新され、有効期限は **2026-09-22** まで。

### 根本解決（スクリプト改造、オンライン + 自己テスト EXIT=0）
- `renew-and-update-clb.sh` を**証明書ごとの直列実行 + 各処理後に sleep 60 + `--no-random-sleep-on-renew`** に変更し、根本からレートリミットを回避。
- `clb-deploy-hook.sh` で証明書名マッピングを修正し、「実際の更新後のみ再バインド」を維持。

### クリーンアップ
- 期限切れで冗長な jsvc 3ドメイン SAN 系譜（退役済み resflow を含む）を削除。
- 平文キーを含む旧 `.bak` スクリプトを削除。
- 平文 tccli キーを 644 の `/etc/crontab` から 600 の `/etc/letsencrypt/tccli.env` へ移行し、3つのスクリプトが統一してここから読み込むように変更；crontab のバックアップを 600 に権限降格。

---

## 6. 運用 Runbook

### 手動更新 + 単一証明書の再バインド（緊急時）
```bash
# 1) 更新（必ず単一、隔離して実行し、一度に複数実行しないこと）
export PATH=/usr/local/bin:$PATH
certbot renew --cert-name <cert-name> \
  --no-random-sleep-on-renew \
  --dns-tencentcloud-propagation-seconds 30

# 2) アップロード + 再バインド（認証情報は /etc/letsencrypt/tccli.env から自動読み込み）
/usr/local/bin/clb-bind-one.sh \
  /etc/letsencrypt/live/<cert-name> <alias> <lb-id> <listener-id>
```

### CLB オンライン証明書の検証
```bash
echo | openssl s_client -connect <ドメイン>:443 -servername <ドメイン> 2>/dev/null \
  | openssl x509 -noout -subject -dates
```

### 証明書一覧 / 期限の確認
```bash
certbot certificates
```

### ドメイン/証明書の追加
1. `certbot certonly --dns-tencentcloud --dns-tencentcloud-credentials /etc/letsencrypt/tencentcloud.ini -d <ドメイン>`
2. `clb-deploy-hook.sh` の `case` にマッピングを追加；`renew-and-update-clb.sh` の `CERTS=(...)` に cert-name を追加。
3. 初回のみ手動で `clb-bind-one.sh` でバインド。

### 未完了 / 改善項目
- [ ] **tccli API Key のローテーション**（`AKIDqgLrCLI2...`）：644 の crontab で平文保存されていた期間が数ヶ月に及び、漏洩として扱う必要があります。
- [ ] **証明書の残り日数アラート追加**（既存の Prometheus + DingTalk に接続）、残り < 14 日でアラート；「スクリプトがエラーを返さない」ことに依存しないようにする。
- [ ] `certbot-dns-tencentcloud` プラグインにリトライ/指数バックオフを追加することを検討（注意：site-packages の変更はアップグレードで上書きされる可能性があります；現在はスクリプト層での直列実行 + 負荷分散で十分対応可能）。
