すべてのプロダクト
Search
ドキュメントセンター

Container Service for Kubernetes:ACK 専用クラスターの etcd 証明書を更新する

最終更新日:Nov 09, 2025

サービスの継続性を確保し、証明書の漏洩やキーのクラッキングによる潜在的なセキュリティリスクを軽減するために、システム通知を受け取ったら、ACK 専用クラスター内のマスターノードの etcd 証明書を速やかに更新することをお勧めします。このトピックでは、ACK 専用クラスター内のマスターノードの etcd 証明書を更新する方法について説明します。

背景情報

ACK 専用クラスターから ACK マネージド Pro クラスターにワークロードを移行できます。クラスターを ACK マネージド Pro クラスターに移行することを選択できます。ACK マネージド Pro クラスターの etcd および Kubernetes コントロールプレーン証明書は Alibaba Cloud によって管理されます。ACK 専用クラスターを移行した後は、以下の更新操作を実行する必要はありません。移行の実行方法の詳細については、「ACK 専用クラスターを ACK マネージド Pro クラスターにホットマイグレーションする」をご参照ください。

使用上の注意

  • Container Service for Kubernetes (ACK) は、etcd 証明書の有効期限が切れる 2 か月前に内部メッセージとショートメッセージでリマインダーを送信し、[クラスター] ページに [Etcd 証明書の更新] ボタンを表示します。

  • 更新プロセス中、クラスターのマスターノード上の API Server、etcd、kube-controller-manager、kubelet などのコントロールプレーンコンポーネントが 1 つずつ再起動されます。この期間中、API Server への永続的な接続は中断されます。この操作はオフピーク時に実行することをお勧めします。更新プロセスが完了するまでに約 30 分かかります。

  • ACK 専用クラスターで etcd または Kubernetes のデフォルトの構成ファイルディレクトリを変更した場合は、証明書を更新する前に元のディレクトリへのシンボリックリンクを作成する必要があります。そうしないと、更新は失敗します。

  • 証明書を手動で更新しても、ACK コンソールに [ETCD 証明書の更新] の有効期限リマインダーがまだ表示されている場合は、して、チケットを送信してリマインダーを削除できます。

  • 更新プロセスが失敗した場合は、して、チケットを送信してサポートを依頼してください。

シナリオ 1: 有効期限が切れていない etcd 証明書を更新する

etcd 証明書の有効期限が近づいており、リマインダーが表示された場合は、次のいずれかの方法で証明書を更新できます。

コンソールを使用して etcd 証明書を自動的に更新する

  1. ACK コンソール。左側のナビゲーションウィンドウで、[クラスター] をクリックします。

  2. etcd 証明書の有効期限が近づいているクラスターの横にある [ETCD 証明書の更新] をクリックします。[証明書の更新] ページで、[証明書の更新] をクリックします。

    説明

    クラスターの etcd 証明書が 2 か月以内に有効期限切れになる場合、クラスターの横に [ETCD 証明書の更新] ボタンが表示されます。

    etcd

  3. [注意] ダイアログボックスで、[OK] をクリックします。

    証明書が更新されると、次の変更が発生します。

    • [証明書の更新] ページに、[正常に更新されました] というメッセージが表示されます。

    • [クラスター] ページでは、クラスターの横にある [ETCD 証明書の更新] ボタンが表示されなくなります。

etcd 証明書を手動で更新する

シナリオ

  • ACK 専用クラスターの etcd 証明書の有効期限が近づいています。

  • テンプレートをデプロイして etcd 証明書を自動的に更新することはできません。

  • コンソールで etcd 証明書を更新することはできません。

これらのシナリオでは、クラスター管理者は任意のマスターノードにログインし、次のスクリプトを実行して etcd 証明書を手動で更新できます。

説明

次のスクリプトは root ユーザーとして実行する必要があります。

  1. マスターノード間で root ユーザーのパスワードなしのログインが設定されていることを確認します。

    Secure Shell (SSH) を使用して、あるマスターノードから別のマスターノードにログインします。パスワードの入力を求められた場合は、マスターノード間でパスワードなしのログインを設定する必要があります。

    # 1. キーを生成します。ノードにログオンキーが既に存在する場合は、このステップをスキップできます。
    ssh-keygen -t rsa
    
    # 2. ssh-copy-id ツールを使用して、公開鍵を他のすべてのマスターノードに転送します。$(internal-ip) は、別のマスターノードの内部 IP アドレスです。
    ssh-copy-id -i ~/.ssh/id_rsa.pub $(internal-ip)
    説明

    パスワードなしのログインを設定しない場合は、スクリプトの実行時に root パスワードを入力する必要があります。

  2. 次のスクリプトをコピーし、restart-apiserver.sh および rotate-etcd.sh として保存し、2 つのファイルを同じフォルダーに保存します。

    説明

    rotate-etcd.sh スクリプトは、ノードのメタデータサービスにアクセスしてリージョン情報を取得し、そのリージョンの最も近い場所からローテーションイメージをプルしようとします。--region xxxx パラメーターを指定してスクリプトを実行し、リージョンを指定することもできます。

    Restart-apiserver.sh スクリプトの表示

    #! /bin/bash
    
    declare -x cmd
    
    k8s::wait_apiserver_ready() {
      set -e
      for i in $(seq 600); do
        if kubectl cluster-info &>/dev/null; then
          return 0
        else
          echo "wait apiserver to be ready, retry ${i}th after 1s"
          sleep 1
        fi
      done
      echo "failed to wait apiserver to be ready"
      return 1
    }
    
    function check_container_runtime() {
      if command -v dockerd &>/dev/null && ps aux | grep -q "[d]ockerd"; then
        cmd=docker
      elif command -v containerd &>/dev/null && ps aux | grep -q "[c]ontainerd"; then
        cmd=crictl
      else
        echo "Neither Dockerd nor Containerd is installed or running."
        exit 1
      fi
    }
    
    function restart_apiserver() {
      # コンテナーランタイムを確認します。
      if [[ $cmd == "docker" ]]; then
        # docker コマンドを使用して kube-apiserver Pod を再起動します。
        container_id=$(docker ps | grep kube-apiserver | awk '{print $1}' | head -n 1 )
        if [[ -n $container_id ]]; then
          echo "Restarting kube-apiserver pod using Docker: $container_id"
          docker restart "${container_id}"
        else
          echo "kube-apiserver pod not found."
        fi
      elif [[ $cmd == "crictl" ]]; then
        # crictl コマンドを使用して kube-apiserver Pod を再起動します。
        pod_id=$(crictl pods --label component=kube-apiserver --latest --state=ready | grep -v "POD ID" | head -n 1 | awk '{print $1}')
        if [[ -n $pod_id ]]; then
          echo "Restarting kube-apiserver pod using crictl: $pod_id"
          crictl stopp "${pod_id}"
        else
          echo "kube-apiserver pod not found."
        fi
      else
        echo "Unsupported container runtime: $cmd"
      fi
      k8s::wait_apiserver_ready
    }
    
    check_container_runtime
    restart_apiserver
    echo "API Server restarted"

    Rotate-etcd.sh スクリプトの表示

    #!/bin/bash
    
    set -eo pipefail
    
    declare -x TARGET_TEAR
    declare -x cmd
    dir=/tmp/etcdcert
    KUBE_CERT_PATH=/etc/kubernetes/pki
    ETCD_CERT_DIR=/var/lib/etcd/cert
    ETCD_HOSTS=""
    currentDir="$PWD"
    
    # Kubernetes 証明書を更新します。デフォルトのイメージリージョン cn-hangzhou をクラスターのリージョンに置き換えます。
    function get_etcdhosts() {
      name1=$(find "$ETCD_CERT_DIR" -name '*-name-1.pem' -exec basename {} \; | sed 's/-name-1.pem//g')
      name2=$(find "$ETCD_CERT_DIR" -name '*-name-2.pem' -exec basename {} \; | sed 's/-name-2.pem//g')
      name3=$(find "$ETCD_CERT_DIR" -name '*-name-3.pem' -exec basename {} \; | sed 's/-name-3.pem//g')
    
      echo "hosts: $name1 $name2 $name3"
      ETCD_HOSTS="$name1 $name2 $name3"
    }
    
    function gencerts() {
      echo "generate ssl cert ..."
      rm -rf $dir
      mkdir -p "$dir"
    
      local hosts
      hosts=$(echo $ETCD_HOSTS | tr -s " " ",")
    
      echo "-----generate ca"
      echo '{"CN":"CA","key":{"algo":"rsa","size":2048}, "ca": {"expiry": "438000h"}}' |
        cfssl gencert -initca - | cfssljson -bare $dir/ca -
      echo '{"signing":{"default":{"expiry":"438000h","usages":["signing","key encipherment","server auth","client auth"]}}}' >$dir/ca-config.json
    
      echo "-----generate etcdserver"
      export ADDRESS=$hosts,ext1.example.com,coreos1.local,coreos1,127.0.0.1
      export NAME=etcd-server
      echo '{"CN":"'$NAME'","hosts":[""],"key":{"algo":"rsa","size":2048}}' |
        cfssl gencert -config=$dir/ca-config.json -ca=$dir/ca.pem -ca-key=$dir/ca-key.pem -hostname="$ADDRESS" - | cfssljson -bare $dir/$NAME
      export ADDRESS=
      export NAME=etcd-client
      echo '{"CN":"'$NAME'","hosts":[""],"key":{"algo":"rsa","size":2048}}' |
        cfssl gencert -config=$dir/ca-config.json -ca=$dir/ca.pem -ca-key=$dir/ca-key.pem -hostname="$ADDRESS" - | cfssljson -bare $dir/$NAME
    
      # ピア CA を生成
      echo "-----generate peer certificates"
      echo '{"CN":"Peer-CA","key":{"algo":"rsa","size":2048}, "ca": {"expiry": "438000h"}}' | cfssl gencert -initca - | cfssljson -bare $dir/peer-ca -
      echo '{"signing":{"default":{"expiry":"438000h","usages":["signing","key encipherment","server auth","client auth"]}}}' >$dir/peer-ca-config.json
      i=0
      for host in $ETCD_HOSTS; do
        ((i = i + 1))
        export MEMBER=${host}-name-$i
        echo '{"CN":"'${MEMBER}'","hosts":[""],"key":{"algo":"rsa","size":2048}}' |
          cfssl gencert -ca=$dir/peer-ca.pem -ca-key=$dir/peer-ca-key.pem -config=$dir/peer-ca-config.json -profile=peer \
            -hostname="$hosts,${MEMBER}.local,${MEMBER}" - | cfssljson -bare $dir/${MEMBER}
      done
    
      # CA バンドルを作成します。
      cat $KUBE_CERT_PATH/etcd/ca.pem >>$dir/bundle_ca.pem
      cat $ETCD_CERT_DIR/ca.pem >>$dir/bundle_ca.pem
      cat $dir/ca.pem >>$dir/bundle_ca.pem
    
      # ピア CA バンドルを作成します。
      cat $ETCD_CERT_DIR/peer-ca.pem >$dir/bundle_peer-ca.pem
      cat $dir/peer-ca.pem >>$dir/bundle_peer-ca.pem
    
      current_year=$(date +%Y)
      TARGET_TEAR=$((TARGET_TEAR + 50))
    
      # chown
      chown -R etcd:etcd $dir
      chmod 0644 $dir/*
    }
    
    function etcd_client_urls() {
      local etcd_hosts=()
      for ip in "${ETCD_HOSTS[@]}"; do
        etcd_hosts+=("https://$ip:2379")
      done
      local result=$(
        IFS=','
        echo "${etcd_hosts[*]}"
      )
      echo "$result"
    }
    
    function check_cert_files_exist() {
      REQUIRED_CERTS=("ca.pem" "etcd-server-key.pem" "etcd-server.pem" "peer-ca-key.pem" "peer-ca.pem")
      if [ ! -d "$ETCD_CERT_DIR" ]; then
        echo "Error: Directory $ETCD_CERT_DIR does not exist"
        exit 1
      fi
    
      for cert_file in "${REQUIRED_CERTS[@]}"; do
        if [ ! -f "$ETCD_CERT_DIR/$cert_file" ]; then
          echo "Error: File $ETCD_CERT_DIR/$cert_file does not exist"
          exit 1
        fi
      done
    
      echo "All required certificate files exist"
    }
    
    function check_etcd_cluster_ready() {
      local etcd_endpoints=()
      for ip in $ETCD_HOSTS; do
        etcd_endpoints+=("https://$ip:2379")
      done
      ready=0
      for i in $(seq 300); do
        for idx in "${!etcd_endpoints[@]}"; do
          endpoint="${etcd_endpoints[$idx]}"
          local health_output=$(ETCDCTL_API=3 etcdctl --cacert=/var/lib/etcd/cert/ca.pem --cert=/var/lib/etcd/cert/etcd-server.pem --key=/var/lib/etcd/cert/etcd-server-key.pem --endpoints "$endpoint" endpoint health --command-timeout=1s 2>&1)
          if echo "$health_output" | grep -q "successfully committed proposal"; then
              unset 'etcd_endpoints[$idx]'
          else
              echo "etcdctl result: ${health_output}"
              echo "$endpoint is not ready"
          fi
        done
        # shellcheck disable=SC2199
        if [[ -z "${etcd_endpoints[@]}" ]]; then
          echo "ETCD cluster is ready"
          ready=1
          break
        fi
        printf "wait etcd cluster to be ready, retry %d after 1s,total 300s \n" "$i"
      done
    }
    
    function check_container_runtime() {
      if command -v dockerd &>/dev/null && ps aux | grep -q "[d]ockerd"; then
        cmd=docker
      elif command -v containerd &>/dev/null && ps aux | grep -q "[c]ontainerd"; then
        cmd=crictl
      else
        echo "Neither Dockerd nor Containerd is installed or running."
        exit 1
      fi
    }
    
    function rotate_etcd_ca() {
      for ADDR in $ETCD_HOSTS; do
        echo "update etcd CA on node $ADDR"
        scp -o StrictHostKeyChecking=no $dir/bundle_ca.pem root@$ADDR:$ETCD_CERT_DIR/ca.pem
        scp -o StrictHostKeyChecking=no $dir/bundle_ca.pem root@$ADDR:$KUBE_CERT_PATH/etcd/ca.pem
        scp -o StrictHostKeyChecking=no $dir/etcd-client.pem root@$ADDR:$KUBE_CERT_PATH/etcd/etcd-client.pem
        scp -o StrictHostKeyChecking=no $dir/etcd-client-key.pem root@$ADDR:$KUBE_CERT_PATH/etcd/etcd-client-key.pem
        scp -o StrictHostKeyChecking=no $dir/bundle_peer-ca.pem root@$ADDR:$ETCD_CERT_DIR/peer-ca.pem
    
        ssh -o StrictHostKeyChecking=no root@$ADDR chown -R etcd:etcd $ETCD_CERT_DIR
        ssh -o StrictHostKeyChecking=no root@$ADDR chmod 0644 $ETCD_CERT_DIR/*
        echo "restart etcd on node $ADDR"
        ssh -o StrictHostKeyChecking=no root@$ADDR systemctl restart etcd
        echo "etcd on node $ADDR restarted"
    
        # etcd が起動しているか、クラスターが正常かどうかを確認します。
        echo "check connectivity for etcd nodes"
        check_etcd_cluster_ready
        echo "end to check connectivity for etcd nodes"
        restart_one_apiserver $ADDR
        echo "apiserver on node $ADDR restarted"
      done
    }
    
    function rotate_etcd_certs() {
      for ADDR in $ETCD_HOSTS; do
        echo "update etcd peer certs on node $ADDR"
        scp -o StrictHostKeyChecking=no \
          $dir/{peer-ca-key.pem,etcd-server.pem,etcd-server-key.pem,etcd-client.pem,etcd-client-key.pem,ca-key.pem,*-name*.pem} root@$ADDR:$ETCD_CERT_DIR/
    
        ssh -o StrictHostKeyChecking=no root@$ADDR chown -R etcd:etcd $ETCD_CERT_DIR
    
        ssh -o StrictHostKeyChecking=no root@$ADDR \
          chmod 0400 $ETCD_CERT_DIR/{peer-ca-key.pem,etcd-server.pem,etcd-server-key.pem,etcd-client.pem,etcd-client-key.pem,ca-key.pem,*-name*.pem}
    
        echo "restart etcd on node $ADDR"
        ssh -o StrictHostKeyChecking=no root@$ADDR systemctl restart etcd
        echo "etcd on node $ADDR restarted"
        echo "check connectivity for etcd nodes"
        check_etcd_cluster_ready
        echo "end to check connectivity for etcd nodes"
      done
    }
    
    function recover_etcd_ca() {
      # etcd ノードの証明書を更新します。
      for ADDR in $ETCD_HOSTS; do
        echo "replace etcd CA on node $ADDR"
        scp -o StrictHostKeyChecking=no $dir/ca.pem root@$ADDR:$ETCD_CERT_DIR/ca.pem
        scp -o StrictHostKeyChecking=no $dir/ca.pem root@$ADDR:$KUBE_CERT_PATH/etcd/ca.pem
        scp -o StrictHostKeyChecking=no $dir/ca.pem root@$ADDR:$KUBE_CERT_PATH/etcd/ca.pem
        scp -o StrictHostKeyChecking=no $dir/peer-ca.pem root@$ADDR:$ETCD_CERT_DIR/peer-ca.pem
        ssh -o StrictHostKeyChecking=no root@$ADDR chown -R etcd:etcd $ETCD_CERT_DIR
        echo "restart apiserver on node $ADDR"
        restart_one_apiserver $ADDR
        echo "apiserver on node $ADDR restarted"
        echo "restart etcd on node $ADDR"
        ssh -o StrictHostKeyChecking=no root@$ADDR systemctl restart etcd
        echo "etcd on node $ADDR restarted"
        echo "check connectivity for etcd nodes"
        check_etcd_cluster_ready
        echo "end to check connectivity for etcd nodes"
        sleep 5
      done
    }
    
    function recover_etcd_client_ca() {
      # etcd ノードの証明書を更新します。
      for ADDR in $ETCD_HOSTS; do
        echo "replace etcd CA on node $ADDR"
        scp -o StrictHostKeyChecking=no $dir/ca.pem root@$ADDR:$KUBE_CERT_PATH/etcd/ca.pem
        scp -o StrictHostKeyChecking=no $dir/ca.pem root@$ADDR:$KUBE_CERT_PATH/etcd/ca.pem
      done
    }
    
    function renew_k8s_certs() {
      # パラメーターで指定されていない場合は、メタサーバーからリージョン ID を取得しようとします
      META_REGION=$(get_region_id)
      if [[ -z "$REGION" ]]; then
        if [[ -z "$META_REGION" ]]; then
            echo "failed to get region id from ECS meta-server, please enter the region parameter."
            return 1
        fi
        REGION=$META_REGION
      elif [[ -n "${META_REGION}" && "$REGION" != "$META_REGION" ]] ; then
        echo "switch to use local region id $META_REGION"
        REGION=$META_REGION
      fi
      # k8s コンポーネントと kubeconfig の証明書を更新します
      for ADDR in $ETCD_HOSTS; do
        echo "renew k8s components cert on node $ADDR"
        #containerd と互換性があります
        set +e
        IMAGE="registry.$REGION.aliyuncs.com/acs/etcd-rotate:v2.0.0"
        if is_vpc; then
          IMAGE="registry-vpc.$REGION.aliyuncs.com/acs/etcd-rotate:v2.0.0"
        fi
        echo "will pull rotate image $IMAGE"
        ssh -o StrictHostKeyChecking=no root@$ADDR docker run --privileged=true  -v /:/alicoud-k8s-host --pid host --net host \
                 $IMAGE /renew/upgrade-k8s.sh --role master
        ssh -o StrictHostKeyChecking=no root@$ADDR ctr image pull $IMAGE
        ssh -o StrictHostKeyChecking=no root@$ADDR ctr run --privileged=true --mount type=bind,src=/,dst=/alicoud-k8s-host,options=rbind:rw \
                --net-host $IMAGE cert-rotate /renew/upgrade-k8s.sh --role master
        set -e
        echo "finished renew k8s components cert on $ADDR"
      done
    }
    
    function get_region_id() {
        set +e; # エラー出力を閉じる
        local TOKEN=`curl -X PUT "http://100.100.100.200/latest/api/token" \
        -H "X-aliyun-ecs-metadata-token-ttl-seconds:900"`
        local path=100.100.100.200/latest/meta-data/region-id
        for (( i=0; i<3; i++));
        do
            response=$(curl -H "X-aliyun-ecs-metadata-token: $TOKEN" --retry 1 --retry-delay 5 -sSL $path)
            if [[ $? -gt 0 || "x$response" == "x" ]];
            then
                sleep 2; continue
            fi
            if echo "$response"|grep -E "<title>.*</title>" >/dev/null;
            then
                sleep 3; continue
            fi
            echo "$response"
            # メタデータからのリターンに成功しました。
            set -e; return
        done
        set -e # エラー出力を開く
        # 失敗した場合、関数は空の文字列を返します
    }
    
    function is_vpc() {
        # curl コマンドを実行し、ECS メタサーバーからネットワークタイプをキャプチャします
        local TOKEN=`curl -X PUT "http://100.100.100.200/latest/api/token" \
        -H "X-aliyun-ecs-metadata-token-ttl-seconds:900"`
        response=$(curl -H "X-aliyun-ecs-metadata-token: $TOKEN" -s http://100.100.100.200/latest/meta-data/network-type)
        if [ "$response" = "vpc" ]; then
          return 0
        else
          return 1
        fi
    }
    
    function generate_cm() {
      echo "generate status configmap"
    
      cat <<-"EOF" >/tmp/ack-rotate-etcd-ca-cm.yaml.tpl
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: ack-rotate-etcd-status
      namespace: kube-system
    data:
      status: "success"
      hosts: "$hosts"
    EOF
    
      sed -e "s#\$hosts#$ETCD_HOSTS#" /tmp/ack-rotate-etcd-ca-cm.yaml.tpl | kubectl apply -f -
    }
    
    function restart_one_apiserver() {
      ADDR=$1
      if [[ -z "${ADDR}" ]]; then
        printf "ADDR is empty,exit."
        exit 1
      fi
      printf "restart apiserver on node %s\n" "${ADDR}"
      scp -o StrictHostKeyChecking=no "${currentDir}"/restart-apiserver.sh root@"${ADDR}":/tmp/restart-apiserver.sh
      ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" chmod +x /tmp/restart-apiserver.sh
      ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" bash /tmp/restart-apiserver.sh
    }
    
    while
        [[ $# -gt 0 ]]
    do
        key="$1"
    
        case $key in
        --region)
          export REGION=$2
          shift
          ;;
        *)
          echo "unknown option [$key]"
          exit 1
          ;;
        esac
        shift
    done
    
    get_etcdhosts
    echo "${ETCD_HOSTS[@]}"
    
    check_container_runtime
    
    # etcd ノードの証明書を更新します。
    echo "---restart runtime and kubelet on master nodes---"
    for ADDR in $ETCD_HOSTS; do
      if [ "$cmd" == "docker" ]; then
        echo "restart docker on node $ADDR"
        ssh -o StrictHostKeyChecking=no root@$ADDR systemctl restart docker
      fi
      ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" systemctl restart kubelet
    done
    sleep 5
    echo "---end to restart runtime and kubelet on master nodes---"
    
    echo "---renew k8s components certs---"
    renew_k8s_certs
    echo "---end to renew k8s components certs---"
    
    echo "---check cert files exist---"
    check_cert_files_exist
    echo "---end to check cert files exist---"
    
    echo "---check connectivity for etcd nodes---"
    check_etcd_cluster_ready
    echo "---end to check connectivity for etcd nodes---"
    
    # etcd ノードの証明書を更新します。
    for ADDR in $ETCD_HOSTS; do
      scp -o StrictHostKeyChecking=no restart-apiserver.sh root@$ADDR:/tmp/restart-apiserver.sh
      ssh -o StrictHostKeyChecking=no root@$ADDR chmod +x /tmp/restart-apiserver.sh
    done
    
    gencerts
    
    echo "---rotate etcd ca and etcd client ca---"
    rotate_etcd_ca
    echo "---end to rotate etcd ca and etcd client ca---"
    
    echo "---rotate etcd peer and certs---"
    rotate_etcd_certs
    echo "---end to rotate etcd peer and certs---"
    
    echo "check etcd cluster ready"
    check_etcd_cluster_ready
    
    echo "---replace etcd ca---"
    recover_etcd_ca
    echo "---end to replace etcd ca---"
    
    generate_cm
    echo "etcd CA and certs have succesfully rotated!"
  3. 任意のマスターノードで、bash rotate-etcd.sh を実行します。

    コマンドの出力が etcd CA and certs have successfully rotated! の場合、すべてのマスターノードの証明書と Kubernetes 証明書は正常に更新されます。

  4. 証明書が更新されたことを確認します。

    cd /var/lib/etcd/cert
    for i in `ls | grep pem| grep -v key`;do openssl x509 -noout -text -in $i | grep -i after && echo "$i" ;done
    
    
    cd /etc/kubernetes/pki/etcd
    for i in `ls | grep pem| grep -v key`;do openssl x509 -noout -text -in $i | grep -i after && echo "$i" ;done
    
    
    cd /etc/kubernetes/pki/
    for i in `ls | grep crt| grep -v key`;do openssl x509 -noout -text -in $i | grep -i after && echo "$i" ;done
    説明
    • 上記のスクリプトの出力の有効期限が 50 年後の場合、更新は完了です。

    • 証明書を手動で更新した後も、ACK コンソールが更新結果を取得できないため、コンソールでクラスターの更新ボタンが表示され続ける場合があります。ボタンを削除するには、して、チケットを送信してください。

シナリオ 2: 有効期限が切れた etcd 証明書を更新する

シナリオ

  • etcd 証明書の有効期限が切れています。

  • API Server にアクセスできない場合は、etcd 証明書をローテーションできます。

  • テンプレートをデプロイして etcd 証明書を自動的に更新することはできません。

  • コンソールで etcd 証明書を更新することはできません。

これらのシナリオでは、クラスター管理者は任意のマスターノードにログインし、次のスクリプトを実行して etcd 証明書を手動で更新できます。

説明

次のスクリプトは root ユーザーとして実行する必要があります。

  1. マスターノード間で root ユーザーのパスワードなしのログインが設定されていることを確認します。

    Secure Shell (SSH) を使用して、あるマスターノードから別のマスターノードにログインします。パスワードの入力を求められた場合は、マスターノード間でパスワードなしのログインを設定する必要があります。

    # 1. キーを生成します。ノードにログオンキーが既に存在する場合は、このステップをスキップできます。
    ssh-keygen -t rsa
    
    # 2. ssh-copy-id ツールを使用して、公開鍵を他のすべてのマスターノードに転送します。$(internal-ip) は、別のマスターノードの内部 IP アドレスです。
    ssh-copy-id -i ~/.ssh/id_rsa.pub $(internal-ip)
    説明

    パスワードなしのログインを設定しない場合は、スクリプトの実行時に root パスワードを入力する必要があります。

  2. 次のスクリプトをコピーし、restart-apiserver.sh および rotate-etcd.sh として保存し、2 つのファイルを同じフォルダーに保存します。

    説明

    rotate-etcd.sh スクリプトは、ノードのメタデータサービスにアクセスしてリージョン情報を取得し、そのリージョンの最も近い場所からローテーションイメージをプルしようとします。--region xxxx パラメーターを指定してスクリプトを実行し、リージョンを指定することもできます。

    Restart-apiserver.sh スクリプトの表示

    #! /bin/bash
    
    declare -x cmd
    
    k8s::wait_apiserver_ready() {
      set -e
      for i in $(seq 600); do
        if kubectl cluster-info &>/dev/null; then
          return 0
        else
          echo "wait apiserver to be ready, retry ${i}th after 1s"
          sleep 1
        fi
      done
      echo "failed to wait apiserver to be ready"
      return 1
    }
    
    function check_container_runtime() {
      if command -v dockerd &>/dev/null && ps aux | grep -q "[d]ockerd"; then
        cmd=docker
      elif command -v containerd &>/dev/null && ps aux | grep -q "[c]ontainerd"; then
        cmd=crictl
      else
        echo "Neither Dockerd nor Containerd is installed or running."
        exit 1
      fi
    }
    
    function restart_apiserver() {
      # コンテナーランタイムを確認します。
      if [[ $cmd == "docker" ]]; then
        # docker コマンドを使用して kube-apiserver Pod を再起動します。
        container_id=$(docker ps | grep kube-apiserver | awk '{print $1}' | head -n 1 )
        if [[ -n $container_id ]]; then
          echo "Restarting kube-apiserver pod using Docker: $container_id"
          docker restart "${container_id}"
        else
          echo "kube-apiserver pod not found."
        fi
      elif [[ $cmd == "crictl" ]]; then
        # crictl コマンドを使用して kube-apiserver Pod を再起動します。
        pod_id=$(crictl pods --label component=kube-apiserver --latest --state=ready | grep -v "POD ID" | head -n 1 | awk '{print $1}')
        if [[ -n $pod_id ]]; then
          echo "Restarting kube-apiserver pod using crictl: $pod_id"
          crictl stopp "${pod_id}"
        else
          echo "kube-apiserver pod not found."
        fi
      else
        echo "Unsupported container runtime: $cmd"
      fi
      k8s::wait_apiserver_ready
    }
    
    check_container_runtime
    restart_apiserver
    echo "API Server restarted"

    Rotate-etcd.sh スクリプトの表示

    #!/bin/bash
    
    set -eo pipefail
    
    declare -x TARGET_TEAR
    declare -x cmd
    dir=/tmp/rollback/etcdcert
    KUBE_CERT_PATH=/etc/kubernetes/pki
    ETCD_CERT_DIR=/var/lib/etcd/cert
    ETCD_HOSTS=""
    currentDir="$PWD"
    
    # Kubernetes 証明書を更新します。デフォルトのイメージリージョン cn-hangzhou をクラスターのリージョンに置き換えます。
    function get_etcdhosts() {
      name1=$(find "$ETCD_CERT_DIR" -name '*-name-1.pem' -exec basename {} \; | sed 's/-name-1.pem//g')
      name2=$(find "$ETCD_CERT_DIR" -name '*-name-2.pem' -exec basename {} \; | sed 's/-name-2.pem//g')
      name3=$(find "$ETCD_CERT_DIR" -name '*-name-3.pem' -exec basename {} \; | sed 's/-name-3.pem//g')
    
      echo "hosts: $name1 $name2 $name3"
      ETCD_HOSTS="$name1 $name2 $name3"
    }
    
    function gencerts() {
      echo "generate ssl cert ..."
      rm -rf $dir
      mkdir -p "$dir"
      cd $dir
    
      local hosts
      hosts=$(echo $ETCD_HOSTS | tr -s " " ",")
    
      echo "generate ca"
      echo '{"CN":"CA","key":{"algo":"rsa","size":2048}, "ca": {"expiry": "438000h"}}' |
        cfssl gencert -initca - | cfssljson -bare $dir/ca -
      echo '{"signing":{"default":{"expiry":"438000h","usages":["signing","key encipherment","server auth","client auth"]}}}' >$dir/ca-config.json
    
      echo "generate etcd server certificates"
      export ADDRESS=$hosts,ext1.example.com,coreos1.local,coreos1,127.0.0.1
      export NAME=etcd-server
      echo '{"CN":"'$NAME'","hosts":[""],"key":{"algo":"rsa","size":2048}}' |
        cfssl gencert -config=$dir/ca-config.json -ca=$dir/ca.pem -ca-key=$dir/ca-key.pem -hostname="$ADDRESS" - | cfssljson -bare $dir/$NAME
      export ADDRESS=
      export NAME=etcd-client
      echo '{"CN":"'$NAME'","hosts":[""],"key":{"algo":"rsa","size":2048}}' |
        cfssl gencert -config=$dir/ca-config.json -ca=$dir/ca.pem -ca-key=$dir/ca-key.pem -hostname="$ADDRESS" - | cfssljson -bare $dir/$NAME
    
      # ピア CA を生成
      echo "generate peer certificates"
      echo '{"CN":"Peer-CA","key":{"algo":"rsa","size":2048}, "ca": {"expiry": "438000h"}}' | cfssl gencert -initca - | cfssljson -bare $dir/peer-ca -
      echo '{"signing":{"default":{"expiry":"438000h","usages":["signing","key encipherment","server auth","client auth"]}}}' >$dir/peer-ca-config.json
      i=0
      for host in $ETCD_HOSTS; do
        ((i = i + 1))
        export MEMBER=${host}-name-$i
        echo '{"CN":"'${MEMBER}'","hosts":[""],"key":{"algo":"rsa","size":2048}}' |
          cfssl gencert -ca=$dir/peer-ca.pem -ca-key=$dir/peer-ca-key.pem -config=$dir/peer-ca-config.json -profile=peer \
            -hostname="$hosts,${MEMBER}.local,${MEMBER}" - | cfssljson -bare $dir/${MEMBER}
      done
    
      # chown
      chown -R etcd:etcd $dir
      chmod 0644 $dir/*
    
      for ADDR in $ETCD_HOSTS; do
        printf "sync the certificates of node %s" "${ADDR}"
        ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" mkdir -p "${dir}"
        scp -o StrictHostKeyChecking=no "${dir}"/* root@"${ADDR}":/var/lib/etcd/cert/
        scp -o StrictHostKeyChecking=no "${dir}"/ca.pem "${dir}"/etcd-client.pem "${dir}"/etcd-client-key.pem root@"${ADDR}":/etc/kubernetes/pki/etcd/
      done
    }
    
    function generate_cm() {
      echo "generate status configmap"
    
      cat <<-"EOF" >/tmp/ack-rotate-etcd-ca-cm.yaml.tpl
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: ack-rotate-etcd-status
      namespace: kube-system
    data:
      status: "success"
      hosts: "$hosts"
    EOF
    
      sed -e "s#\$hosts#$ETCD_HOSTS#" /tmp/ack-rotate-etcd-ca-cm.yaml.tpl | kubectl apply -f -
    }
    
    function rotate_etcd() {
      for ADDR in $ETCD_HOSTS; do
        printf "rotate etcd's certificates on node %s\n" "${ADDR}"
          if [ "$cmd" == "docker" ]; then
            echo "restart docker on node $ADDR"
            ssh -e none -o StrictHostKeyChecking=no root@$ADDR systemctl restart docker
          fi
        ssh -e none -o StrictHostKeyChecking=no root@$ADDR systemctl restart etcd
      done
    }
    
    function rotate_apiserver() {
      echo "current dir: $currentDir"
      for ADDR in $ETCD_HOSTS; do
        printf "restart apiserver on node %s\n" "${ADDR}"
        scp -o StrictHostKeyChecking=no "${currentDir}"/restart-apiserver.sh root@"${ADDR}":/tmp/restart-apiserver.sh
        ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" systemctl restart kubelet
        ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" chmod +x /tmp/restart-apiserver.sh
        ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" bash /tmp/restart-apiserver.sh
      done
    }
    
    function check_etcd_cluster_ready() {
      local etcd_endpoints=()
      for ip in $ETCD_HOSTS; do
        etcd_endpoints+=("https://$ip:2379")
      done
    
      for i in $(seq 300); do
        for idx in "${!etcd_endpoints[@]}"; do
          endpoint="${etcd_endpoints[$idx]}"
          local health_output=$(ETCDCTL_API=3 etcdctl --cacert=/var/lib/etcd/cert/ca.pem --cert=/var/lib/etcd/cert/etcd-server.pem --key=/var/lib/etcd/cert/etcd-server-key.pem --endpoints "$endpoint" endpoint health --command-timeout=1s 2>&1)
          if echo "$health_output" | grep -q "successfully committed proposal"; then
              unset 'etcd_endpoints[$idx]'
          else
              echo "etcdctl result: ${health_output}"
              echo "$endpoint is not ready"
          fi
        done
        # shellcheck disable=SC2199
        if [[ -z "${etcd_endpoints[@]}" ]]; then
          echo "ETCD cluster is ready"
          break
        fi
        sleep 1
        printf "wait etcd cluster to be ready, retry %d after 1s,total 300s \n" "$i"
      done
    }
    
    function get_region_id() {
        set +e; # エラー出力を閉じる
        local TOKEN=`curl -X PUT "http://100.100.100.200/latest/api/token" \
        -H "X-aliyun-ecs-metadata-token-ttl-seconds:900"`
        local path=100.100.100.200/latest/meta-data/region-id
        for (( i=0; i<3; i++));
        do
            response=$(curl -H "X-aliyun-ecs-metadata-token: $TOKEN" --retry 1 --retry-delay 5 -sSL $path)
            if [[ $? -gt 0 || "x$response" == "x" ]];
            then
                sleep 2; continue
            fi
            if echo "$response"|grep -E "<title>.*</title>" >/dev/null;
            then
                sleep 3; continue
            fi
            echo "$response"
            # メタデータからのリターンに成功しました。
            set -e; return
        done
        set -e # エラー出力を開く
        # 失敗した場合、関数は空の文字列を返します
    }
    
    function is_vpc() {
        # curl コマンドを実行し、ECS メタサーバーからネットワークタイプをキャプチャします
        local TOKEN=`curl -X PUT "http://100.100.100.200/latest/api/token" \
        -H "X-aliyun-ecs-metadata-token-ttl-seconds:900"`
        response=$(curl -H "X-aliyun-ecs-metadata-token: $TOKEN" -s http://100.100.100.200/latest/meta-data/network-type)
        if [ "$response" = "vpc" ]; then
          return 0
        else
          return 1
        fi
    }
    
    function renew_k8s_certs() {
      # パラメーターで指定されていない場合は、メタサーバーからリージョン ID を取得しようとします
      META_REGION=$(get_region_id)
      if [[ -z "$REGION" ]]; then
        if [[ -z "$META_REGION" ]]; then
            echo "failed to get region id from ECS meta-server, please enter the region parameter."
            return 1
        fi
        REGION=$META_REGION
      elif [[ -n "${META_REGION}" && "$REGION" != "$META_REGION" ]] ; then
        echo "switch to use local region id $META_REGION"
        REGION=$META_REGION
      fi
      # k8s コンポーネントと kubeconfig の証明書を更新します
      for ADDR in $ETCD_HOSTS; do
        echo "renew k8s components cert on node $ADDR"
        #containerd と互換性があります
        set +e
        IMAGE="registry.$REGION.aliyuncs.com/acs/etcd-rotate:v2.0.0"
        if is_vpc; then
          IMAGE="registry-vpc.$REGION.aliyuncs.com/acs/etcd-rotate:v2.0.0"
        fi
        echo "will pull rotate image $IMAGE"
        ssh -o StrictHostKeyChecking=no root@$ADDR docker run --privileged=true  -v /:/alicoud-k8s-host --pid host --net host \
                 $IMAGE /renew/upgrade-k8s.sh --role master
        ssh -o StrictHostKeyChecking=no root@$ADDR ctr image pull $IMAGE
        ssh -o StrictHostKeyChecking=no root@$ADDR ctr run --privileged=true --mount type=bind,src=/,dst=/alicoud-k8s-host,options=rbind:rw \
                --net-host $IMAGE cert-rotate /renew/upgrade-k8s.sh --role master
        set -e
        echo "finished renew k8s components cert on $ADDR"
      done
    }
    
    function check_container_runtime() {
      if command -v dockerd &>/dev/null && ps aux | grep -q "[d]ockerd"; then
        cmd=docker
      elif command -v containerd &>/dev/null && ps aux | grep -q "[c]ontainerd"; then
        cmd=crictl
      else
        echo "Neither Dockerd nor Containerd is installed or running."
        exit 1
      fi
    }
    
    while
        [[ $# -gt 0 ]]
    do
        key="$1"
    
        case $key in
        --region)
          export REGION=$2
          shift
          ;;
        *)
          echo "unknown option [$key]"
          exit 1
          ;;
        esac
        shift
    done
    
    get_etcdhosts
    printf "ETCD_HOSTS: %s\n" "$ETCD_HOSTS"
    
    gencerts
    echo "---generate certificates successfully---"
    
    rotate_etcd
    echo "---rotate etcd successfully---"
    
    echo "---check etcd cluster ready---"
    check_etcd_cluster_ready
    
    rotate_apiserver
    echo "---restart apiserver successfully---"
    
    echo "---renew k8s components certs---"
    renew_k8s_certs
    echo "---end to renew k8s components certs---"
    
    generate_cm
    echo "etcd CA and certs have successfully rotated!"
    
    rm -rf $dir
  1. 証明書が更新されたことを確認します。

cd /var/lib/etcd/cert
for i in `ls | grep pem| grep -v key`;do openssl x509 -noout -text -in $i | grep -i after && echo "$i" ;done


cd /etc/kubernetes/pki/etcd
for i in `ls | grep pem| grep -v key`;do openssl x509 -noout -text -in $i | grep -i after && echo "$i" ;done


cd /etc/kubernetes/pki/
for i in `ls | grep crt| grep -v key`;do openssl x509 -noout -text -in $i | grep -i after && echo "$i" ;done
説明
  • 上記のスクリプトの出力の有効期限が 50 年後の場合、更新は完了です。

  • 証明書を手動で更新した後も、ACK コンソールが更新結果を取得できないため、コンソールでクラスターの期限切れステータスが表示され続ける場合があります。ステータスを削除するには、して、チケットを送信してください。

証明書の更新が失敗した場合のロールバック

シナリオ

  • コンソールからの証明書の更新が失敗しました。

  • コマンドラインからの証明書の更新が失敗しました。

これらのシナリオでは、クラスター管理者は任意のマスターノードにログインし、次のスクリプトを実行して変更をロールバックできます。この操作は、新しい etcd 証明書のセットを生成し、etcd サーバー証明書と kube-apiserver クライアント証明書を更新することで、クラスターを機能する状態に復元します。

説明

次のスクリプトは root ユーザーとして実行する必要があります。

  1. マスターノード間で root ユーザーのパスワードなしのログインが設定されていることを確認します。

    Secure Shell (SSH) を使用して、あるマスターノードから別のマスターノードにログインします。パスワードの入力を求められた場合は、マスターノード間でパスワードなしのログインを設定する必要があります。

    # 1. キーを生成します。ノードにログオンキーが既に存在する場合は、このステップをスキップできます。
    ssh-keygen -t rsa
    
    # 2. ssh-copy-id ツールを使用して、公開鍵を他のすべてのマスターノードに転送します。$(internal-ip) は、別のマスターノードの内部 IP アドレスです。
    ssh-copy-id -i ~/.ssh/id_rsa.pub $(internal-ip)
    説明

    パスワードなしのログインを設定しない場合は、スクリプトの実行時に root パスワードを入力する必要があります。

  2. 次のスクリプトをコピーし、restart-apiserver.sh および rollback-etcd.sh として保存し、2 つのファイルを同じフォルダーに保存します

    説明

    rollback-etcd.sh スクリプトは、ノードのメタデータサービスにアクセスしてリージョン情報を取得し、そのリージョンの最も近い場所からローテーションイメージをプルしようとします。--region xxxx パラメーターを指定してスクリプトを実行し、リージョンを指定することもできます。

    Restart-apiserver.sh スクリプトの表示

    #! /bin/bash
    
    declare -x cmd
    
    k8s::wait_apiserver_ready() {
      set -e
      for i in $(seq 600); do
        if kubectl cluster-info &>/dev/null; then
          return 0
        else
          echo "wait apiserver to be ready, retry ${i}th after 1s"
          sleep 1
        fi
      done
      echo "failed to wait apiserver to be ready"
      return 1
    }
    
    function check_container_runtime() {
      if command -v dockerd &>/dev/null && ps aux | grep -q "[d]ockerd"; then
        cmd=docker
      elif command -v containerd &>/dev/null && ps aux | grep -q "[c]ontainerd"; then
        cmd=crictl
      else
        echo "Neither Dockerd nor Containerd is installed or running."
        exit 1
      fi
    }
    
    function restart_apiserver() {
      # コンテナーランタイムを確認します。
      if [[ $cmd == "docker" ]]; then
        # docker コマンドを使用して kube-apiserver Pod を再起動します。
        container_id=$(docker ps | grep kube-apiserver | awk '{print $1}' | head -n 1 )
        if [[ -n $container_id ]]; then
          echo "Restarting kube-apiserver pod using Docker: $container_id"
          docker restart "${container_id}"
        else
          echo "kube-apiserver pod not found."
        fi
      elif [[ $cmd == "crictl" ]]; then
        # crictl コマンドを使用して kube-apiserver Pod を再起動します。
        pod_id=$(crictl pods --label component=kube-apiserver --latest --state=ready | grep -v "POD ID" | head -n 1 | awk '{print $1}')
        if [[ -n $pod_id ]]; then
          echo "Restarting kube-apiserver pod using crictl: $pod_id"
          crictl stopp "${pod_id}"
        else
          echo "kube-apiserver pod not found."
        fi
      else
        echo "Unsupported container runtime: $cmd"
      fi
      k8s::wait_apiserver_ready
    }
    
    check_container_runtime
    restart_apiserver
    echo "API Server restarted"

    Rollback-etcd.sh スクリプトの表示

    #!/bin/bash
    
    set -eo pipefail
    
    declare -x TARGET_TEAR
    declare -x cmd
    dir=/tmp/rollback/etcdcert
    KUBE_CERT_PATH=/etc/kubernetes/pki
    ETCD_CERT_DIR=/var/lib/etcd/cert
    ETCD_HOSTS=""
    currentDir="$PWD"
    
    # Kubernetes 証明書を更新します。デフォルトのイメージリージョン cn-hangzhou をクラスターのリージョンに置き換えます。
    function get_etcdhosts() {
      name1=$(find "$ETCD_CERT_DIR" -name '*-name-1.pem' -exec basename {} \; | sed 's/-name-1.pem//g')
      name2=$(find "$ETCD_CERT_DIR" -name '*-name-2.pem' -exec basename {} \; | sed 's/-name-2.pem//g')
      name3=$(find "$ETCD_CERT_DIR" -name '*-name-3.pem' -exec basename {} \; | sed 's/-name-3.pem//g')
    
      echo "hosts: $name1 $name2 $name3"
      ETCD_HOSTS="$name1 $name2 $name3"
    }
    
    function gencerts() {
      echo "generate ssl cert ..."
      rm -rf $dir
      mkdir -p "$dir"
      cd $dir
    
      local hosts
      hosts=$(echo $ETCD_HOSTS | tr -s " " ",")
    
      echo "generate ca"
      echo '{"CN":"CA","key":{"algo":"rsa","size":2048}, "ca": {"expiry": "438000h"}}' |
        cfssl gencert -initca - | cfssljson -bare $dir/ca -
      echo '{"signing":{"default":{"expiry":"438000h","usages":["signing","key encipherment","server auth","client auth"]}}}' >$dir/ca-config.json
    
      echo "generate etcd server certificates"
      export ADDRESS=$hosts,ext1.example.com,coreos1.local,coreos1,127.0.0.1
      export NAME=etcd-server
      echo '{"CN":"'$NAME'","hosts":[""],"key":{"algo":"rsa","size":2048}}' |
        cfssl gencert -config=$dir/ca-config.json -ca=$dir/ca.pem -ca-key=$dir/ca-key.pem -hostname="$ADDRESS" - | cfssljson -bare $dir/$NAME
      export ADDRESS=
      export NAME=etcd-client
      echo '{"CN":"'$NAME'","hosts":[""],"key":{"algo":"rsa","size":2048}}' |
        cfssl gencert -config=$dir/ca-config.json -ca=$dir/ca.pem -ca-key=$dir/ca-key.pem -hostname="$ADDRESS" - | cfssljson -bare $dir/$NAME
    
      # ピア CA を生成
      echo "generate peer certificates"
      echo '{"CN":"Peer-CA","key":{"algo":"rsa","size":2048}, "ca": {"expiry": "438000h"}}' | cfssl gencert -initca - | cfssljson -bare $dir/peer-ca -
      echo '{"signing":{"default":{"expiry":"438000h","usages":["signing","key encipherment","server auth","client auth"]}}}' >$dir/peer-ca-config.json
      i=0
      for host in $ETCD_HOSTS; do
        ((i = i + 1))
        export MEMBER=${host}-name-$i
        echo '{"CN":"'${MEMBER}'","hosts":[""],"key":{"algo":"rsa","size":2048}}' |
          cfssl gencert -ca=$dir/peer-ca.pem -ca-key=$dir/peer-ca-key.pem -config=$dir/peer-ca-config.json -profile=peer \
            -hostname="$hosts,${MEMBER}.local,${MEMBER}" - | cfssljson -bare $dir/${MEMBER}
      done
    
      # chown
      chown -R etcd:etcd $dir
      chmod 0644 $dir/*
    
      for ADDR in $ETCD_HOSTS; do
        printf "sync the certificates of node %s" "${ADDR}"
        ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" mkdir -p "${dir}"
        scp -o StrictHostKeyChecking=no "${dir}"/* root@"${ADDR}":/var/lib/etcd/cert/
        scp -o StrictHostKeyChecking=no "${dir}"/ca.pem "${dir}"/etcd-client.pem "${dir}"/etcd-client-key.pem root@"${ADDR}":/etc/kubernetes/pki/etcd/
      done
    }
    
    function generate_cm() {
      echo "generate status configmap"
    
      cat <<-"EOF" >/tmp/ack-rotate-etcd-ca-cm.yaml.tpl
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: ack-rotate-etcd-status
      namespace: kube-system
    data:
      status: "success"
      hosts: "$hosts"
    EOF
    
      sed -e "s#\$hosts#$ETCD_HOSTS#" /tmp/ack-rotate-etcd-ca-cm.yaml.tpl | kubectl apply -f -
    }
    
    function rotate_etcd() {
      for ADDR in $ETCD_HOSTS; do
        printf "rotate etcd's certificates on node %s\n" "${ADDR}"
          if [ "$cmd" == "docker" ]; then
            echo "restart docker on node $ADDR"
            ssh -e none -o StrictHostKeyChecking=no root@$ADDR systemctl restart docker
          fi
        ssh -e none -o StrictHostKeyChecking=no root@$ADDR systemctl restart etcd
      done
    }
    
    function rotate_apiserver() {
      echo "current dir: $currentDir"
      for ADDR in $ETCD_HOSTS; do
        printf "restart apiserver on node %s\n" "${ADDR}"
        scp -o StrictHostKeyChecking=no "${currentDir}"/restart-apiserver.sh root@"${ADDR}":/tmp/restart-apiserver.sh
        ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" systemctl restart kubelet
        ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" chmod +x /tmp/restart-apiserver.sh
        ssh -e none -o StrictHostKeyChecking=no root@"${ADDR}" bash /tmp/restart-apiserver.sh
      done
    }
    
    function check_etcd_cluster_ready() {
      local etcd_endpoints=()
      for ip in $ETCD_HOSTS; do
        etcd_endpoints+=("https://$ip:2379")
      done
    
      for i in $(seq 300); do
        for idx in "${!etcd_endpoints[@]}"; do
          endpoint="${etcd_endpoints[$idx]}"
          local health_output=$(ETCDCTL_API=3 etcdctl --cacert=/var/lib/etcd/cert/ca.pem --cert=/var/lib/etcd/cert/etcd-server.pem --key=/var/lib/etcd/cert/etcd-server-key.pem --endpoints "$endpoint" endpoint health --command-timeout=1s 2>&1)
          if echo "$health_output" | grep -q "successfully committed proposal"; then
              unset 'etcd_endpoints[$idx]'
          else
              echo "etcdctl result: ${health_output}"
              echo "$endpoint is not ready"
          fi
        done
        # shellcheck disable=SC2199
        if [[ -z "${etcd_endpoints[@]}" ]]; then
          echo "ETCD cluster is ready"
          break
        fi
        sleep 1
        printf "wait etcd cluster to be ready, retry %d after 1s,total 300s \n" "$i"
      done
    }
    
    function get_region_id() {
        set +e; # エラー出力を閉じる
        local TOKEN=`curl -X PUT "http://100.100.100.200/latest/api/token" \
        -H "X-aliyun-ecs-metadata-token-ttl-seconds:900"`
        local path=100.100.100.200/latest/meta-data/region-id
        for (( i=0; i<3; i++));
        do
            response=$(curl -H "X-aliyun-ecs-metadata-token: $TOKEN" --retry 1 --retry-delay 5 -sSL $path)
            if [[ $? -gt 0 || "x$response" == "x" ]];
            then
                sleep 2; continue
            fi
            if echo "$response"|grep -E "<title>.*</title>" >/dev/null;
            then
                sleep 3; continue
            fi
            echo "$response"
            # メタデータからのリターンに成功しました。
            set -e; return
        done
        set -e # エラー出力を開く
        # 失敗した場合、関数は空の文字列を返します
    }
    
    function is_vpc() {
        # curl コマンドを実行し、ECS メタサーバーからネットワークタイプをキャプチャします
        local TOKEN=`curl -X PUT "http://100.100.100.200/latest/api/token" \
        -H "X-aliyun-ecs-metadata-token-ttl-seconds:900"`
        response=$(curl -H "X-aliyun-ecs-metadata-token: $TOKEN" -s http://100.100.100.200/latest/meta-data/network-type)
        if [ "$response" = "vpc" ]; then
          return 0
        else
          return 1
        fi
    }
    
    function renew_k8s_certs() {
      # パラメーターで指定されていない場合は、メタサーバーからリージョン ID を取得しようとします
      META_REGION=$(get_region_id)
      if [[ -z "$REGION" ]]; then
        if [[ -z "$META_REGION" ]]; then
            echo "failed to get region id from ECS meta-server, please enter the region parameter."
            return 1
        fi
        REGION=$META_REGION
      elif [[ -n "${META_REGION}" && "$REGION" != "$META_REGION" ]] ; then
        echo "switch to use local region id $META_REGION"
        REGION=$META_REGION
      fi
      # k8s コンポーネントと kubeconfig の証明書を更新します
      for ADDR in $ETCD_HOSTS; do
        echo "renew k8s components cert on node $ADDR"
        #containerd と互換性があります
        set +e
        IMAGE="registry.$REGION.aliyuncs.com/acs/etcd-rotate:v2.0.0"
        if is_vpc; then
          IMAGE="registry-vpc.$REGION.aliyuncs.com/acs/etcd-rotate:v2.0.0"
        fi
        echo "will pull rotate image $IMAGE"
        ssh -o StrictHostKeyChecking=no root@$ADDR docker run --privileged=true  -v /:/alicoud-k8s-host --pid host --net host \
                 $IMAGE /renew/upgrade-k8s.sh --role master
        ssh -o StrictHostKeyChecking=no root@$ADDR ctr image pull $IMAGE
        ssh -o StrictHostKeyChecking=no root@$ADDR ctr run --privileged=true --mount type=bind,src=/,dst=/alicoud-k8s-host,options=rbind:rw \
                --net-host $IMAGE cert-rotate /renew/upgrade-k8s.sh --role master
        set -e
        echo "finished renew k8s components cert on $ADDR"
      done
    }
    
    function check_container_runtime() {
      if command -v dockerd &>/dev/null && ps aux | grep -q "[d]ockerd"; then
        cmd=docker
      elif command -v containerd &>/dev/null && ps aux | grep -q "[c]ontainerd"; then
        cmd=crictl
      else
        echo "Neither Dockerd nor Containerd is installed or running."
        exit 1
      fi
    }
    
    while
        [[ $# -gt 0 ]]
    do
        key="$1"
    
        case $key in
        --region)
          export REGION=$2
          shift
          ;;
        *)
          echo "unknown option [$key]"
          exit 1
          ;;
        esac
        shift
    done
    
    get_etcdhosts
    printf "ETCD_HOSTS: %s\n" "$ETCD_HOSTS"
    
    gencerts
    echo "---generate certificates successfully---"
    
    rotate_etcd
    echo "---rotate etcd successfully---"
    
    echo "---check etcd cluster ready---"
    check_etcd_cluster_ready
    
    rotate_apiserver
    echo "---restart apiserver successfully---"
    
    echo "---renew k8s components certs---"
    renew_k8s_certs
    echo "---end to renew k8s components certs---"
    
    generate_cm
    echo "etcd CA and certs have successfully rotated!"
    
    rm -rf $dir
  3. 任意のマスターノードで、bash rollback-etcd.sh を実行します。

    コマンドの出力が etcd CA and certs have successfully rotated! の場合、すべてのマスターノードの証明書と Kubernetes 証明書は正常に更新されます。

  4. 証明書が更新されたことを確認します。

cd /var/lib/etcd/cert
for i in `ls | grep pem| grep -v key`;do openssl x509 -noout -text -in $i | grep -i after && echo "$i" ;done


cd /etc/kubernetes/pki/etcd
for i in `ls | grep pem| grep -v key`;do openssl x509 -noout -text -in $i | grep -i after && echo "$i" ;done


cd /etc/kubernetes/pki/
for i in `ls | grep crt| grep -v key`;do openssl x509 -noout -text -in $i | grep -i after && echo "$i" ;done
説明

上記のスクリプトによって返される時刻が 50 年以上先の場合、ローテーションは完了です。