Mencipta kluster RKE2 daripada UI Rancher berfungsi, tetapi ia menyembunyikan lapan komponen yang bergantung antara satu sama lain. Apabila kluster tersangkut pada “waiting for agent to check in”, UI tidak memberitahu komponen mana yang salah.

Template ini menggantikan klik dengan satu arahan bagi setiap komponen, dengan satu soalan “kenapa” bagi setiap langkah, supaya apabila ia gagal, anda tahu di mana. Ia mencipta kluster tiga node HA (etcd, control plane dan worker pada setiap node) di atas VM OpenStack melalui node driver OpenStack Rancher. Disahkan terhadap RKE2 v1.34.6+rke2r3 dan Rancher 2.14.

(Prasyarat OpenStack untuk node driver ini dibincangkan dalam Provisioning RKE2 from Rancher? Prepare OpenStack First.)

Penafian. Nilai seperti nama domain, alamat IP, ID rangkaian dan ID pengguna di bawah ialah pemegang tempat. Gantikan dengan nilai anda. insecure: true pada kelayakan OpenStack hanya sesuai untuk lab yang menggunakan sijil tandatangan sendiri.


Gambaran besar: lapan komponen

FASA 0  PREFLIGHT (gate): DNS · server-url · /cacerts · driver · quota/flavor
        ↓ jika gagal, JANGAN teruskan
1. TOKEN          siapa kita di Rancher
2. NODE DRIVER    Rancher tahu cara cakap dengan OpenStack
3. CREDENTIAL     username dan kata laluan OpenStack
4. MACHINE CONFIG cara bina VM: flavor, image, network, SG
5. CLUSTER        berapa node, apa role, versi K8s
        ↓ Rancher terjemah kepada objek
Cluster → MachineSet → Machine ×3 → OpenstackMachine ×3 → VM ×3
        ↓ cloud-init: trust CA → pasang rancher-system-agent → RKE2
node Ready · etcd 3/3

Masa yang realistik: VM ACTIVE sekitar 9 minit, agent dan plan sekitar 5 minit, pemasangan RKE2 sekitar 10 minit. Jumlah sekitar 25 minit. Jangan panik pada mesej “waiting for cluster agent to connect”.

Pembolehubah yang digunakan di bawah:

set -a; source ~/.env; set +a        # source DULU
RANCHER_URL="https://rancher.example.lan"
RANCHER_IP="<rancher-ip>"
RES="--resolve rancher.example.lan:443:$RANCHER_IP"

Sebab “source dahulu”: fail .env saya juga mengandungi RANCHER_URL untuk Rancher lama. Jika anda menetapkannya sebelum source, nilainya ditimpa dan anda log masuk ke server yang salah.

Untuk mengelak mengulang blok log masuk, definisikan sekali:

rancher_token() {
  curl -sk $RES "$RANCHER_URL/v3-public/localProviders/local?action=login" \
    -H 'Content-Type: application/json' \
    -d "{\"username\":\"admin\",\"password\":\"$RANCHER_ADMIN_PW\",\"responseType\":\"json\"}" \
    | python3 -c "import json,sys; print(json.load(sys.stdin)['token'])"
}

Fasa 0: Preflight (gate)

Lulus semua dahulu, baru teruskan.

0.1 DNS mesti wujud pada server DNS yang sebenar.

nslookup rancher.example.lan <dns-server>      # jangka: alamat Rancher

Jika NXDOMAIN, tambah Host Override pada router/firewall. /etc/hosts pada hos admin anda tidak membantu VM; VM menggunakan nameserver rangkaian.

0.2 server-url mesti ditetapkan:

TOK=$(rancher_token)
curl -sk $RES -H "Authorization: Bearer $TOK" "$RANCHER_URL/v3/settings/server-url" \
  | python3 -c "import json,sys; print('server-url =', json.load(sys.stdin).get('value'))"

Jika kosong, cloud-init gagal dengan FATAL: $CATTLE_SERVER was not set. Tetapkan dengan PUT.

0.3 /cacerts: punca nombor satu kluster tergantung.

curl -sk $RES "$RANCHER_URL/cacerts" | wc -c        # jangka: > 0 (saya: 1277)

Jika 0, agent tidak boleh mempercayai Rancher, jadi jangan teruskan. Sahkan puncanya:

kubectl -n cattle-system get deploy rancher \
  -o jsonpath='{.spec.template.spec.containers[0].args}' | tr ' ' '\n' | grep cacerts
# jika keluar "--no-cacerts", itulah puncanya

Penyelesaiannya: cipta secret tls-ca dan tls-ca-additional daripada sijil CA anda, kemudian helm upgrade rancher ... --reuse-values --set privateCA=true --set additionalTrustedCAs=true. Ulang 0.3 sehingga ia memulangkan kandungan.

0.4 Node driver aktif:

curl -sk $RES -H "Authorization: Bearer $TOK" "$RANCHER_URL/v3/nodedrivers/openstack" \
  | python3 -c "import json,sys; print('driver active =', json.load(sys.stdin).get('active'))"

0.5 Kuota dan flavor:

openstack limits show --absolute -c Name -c Value -f value | paste - - | grep -E "total_cores_used|total_instances_used"
openstack flavor list -c Name -c VCPUs -c RAM -c Disk -f value | grep rancher
# flavor: 4 vCPU / 8192 MB / disk 50, disk mesti >= 50

0.6 Tiada kluster tersangkut yang tinggal:

kubectl -n fleet-default get clusters.provisioning.cattle.io,machines --no-headers     # kosong = bersih

Fasa 1: Token

TOK=$(rancher_token), dan sahkan ia keluar (echo "${TOK:0:12}..."). Jika KeyError: 'token', anda log masuk ke server yang salah. Semak RANCHER_URL.

Kenapa: setiap langkah selepas ini menggunakan token ini.

Fasa 2: Node driver

curl -sk $RES -X PUT "$RANCHER_URL/v3/nodedrivers/openstack" \
  -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"active":true}'

Kenapa: memberitahu Rancher “saya mahu guna OpenStack”. Tanpa ini ia tidak tahu menterjemah OpenstackConfig kepada panggilan Nova dan Neutron.

Fasa 3: Cloud credential

Cari dahulu dan guna semula jika ada, untuk mengelak pendua:

CRED_ID=$(curl -sk $RES -H "Authorization: Bearer $TOK" "$RANCHER_URL/v3/cloudCredentials" \
  | python3 -c "import json,sys
for c in json.load(sys.stdin).get('data',[]):
    if c['name']=='openstack-rancher': print(c['id']); break")
echo "CRED_ID=$CRED_ID"

Jika kosong, cipta:

[ -z "$CRED_ID" ] && curl -sk $RES -X POST "$RANCHER_URL/v3/cloudCredentials" \
  -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
  -d "{\"type\":\"cloudCredential\",\"name\":\"openstack-rancher\",
\"openstackcredentialConfig\":{
\"authUrl\":\"http://<keystone-vip>:5000/v3\",
\"username\":\"<rancher-user>\",\"password\":\"$OPENSTACK_PW\",
\"tenantName\":\"<project>\",\"domainName\":\"Default\",
\"region\":\"RegionOne\",\"endpointType\":\"publicURL\",\"insecure\":true}}"

ID yang dipulangkan berbentuk cattle-global-data:cc-xxxxx. Jika awalan tiada, tambahkannya.

Kenapa: Rancher memerlukan kelayakan OpenStack untuk membina VM. Gunakan project khusus, bukan admin (least privilege).

Fasa 4: Machine config (OpenstackConfig)

Ini “resepi VM”.

CLUSTER_NAME="rancher-os-2"
MACHINE_CONFIG="nc-${CLUSTER_NAME}"
K8S_VERSION="v1.34.6+rke2r3"

cat > /tmp/nc.yaml <<YAML
apiVersion: rke-machine-config.cattle.io/v1
kind: OpenstackConfig
metadata:
  name: ${MACHINE_CONFIG}
  namespace: fleet-default
activeTimeout: "1200"
authUrl: http://<keystone-vip>:5000/v3
username: <rancher-user>
password: ${OPENSTACK_PW}
tenantName: <project>
domainName: Default
region: RegionOne
endpointType: publicURL
insecure: true
flavorName: rancher.node
imageName: ubuntu-24.04-cloud
netId: <network-id>
secGroups: rancher-sg
sshUser: ubuntu
sshPort: "22"
keypairName: ""
volumeSize: "0"
bootFromVolume: false
configDrive: true
YAML

kubectl apply --dry-run=server -f /tmp/nc.yaml     # sahkan dahulu
kubectl apply -f /tmp/nc.yaml

Setiap medan di sini ada sebab:

MedanNilaiKenapa
activeTimeout1200Lalai 200 s terlalu singkat; VM di atas Ceph HDD mengambil sekitar 9 minit
flavorNamerancher.nodeDisk 50 GB. disk=0 dengan imej kecil memberi root disk yang kecil
netIdID rangkaianMedan ini sahaja. netName diabaikan oleh driver
secGroupsnama SGNama, bukan ID
sshUserubuntuLalai driver ialah root, yang tidak dibenarkan oleh imej Ubuntu
keypairNamekosongJika diisi, privateKeyFile mesti diberi juga, jika tidak driver menolak
volumeSize0Mengelak bug endpoint Cinder (502)
bootFromVolumefalseBoot ke ephemeral, elak laluan Cinder
configDrivetrueWajib. DHCP pada rangkaian itu tidak berfungsi, jadi IP datang dari config drive

Fasa 5: Kluster

Dapatkan ID pengguna semasa (diperlukan untuk anotasi pencipta):

ADMIN_UID=$(curl -sk $RES -H "Authorization: Bearer $TOK" "$RANCHER_URL/v3/tokens" \
  | python3 -c "import json,sys
for t in json.load(sys.stdin).get('data',[]):
    if t.get('current'): print(t.get('userId'))" | head -1)

Spesifikasi kluster:

cat > /tmp/cluster.json <<JSON
{
  "apiVersion": "provisioning.cattle.io/v1",
  "kind": "Cluster",
  "metadata": {
    "name": "${CLUSTER_NAME}", "namespace": "fleet-default",
    "annotations": { "field.cattle.io/creatorId": "${ADMIN_UID}" }
  },
  "spec": {
    "cloudCredentialSecretName": "${CRED_ID}",
    "kubernetesVersion": "${K8S_VERSION}",
    "rkeConfig": {
      "machinePools": [{
        "name": "cp", "quantity": 3,
        "etcdRole": true, "controlPlaneRole": true, "workerRole": true,
        "machineConfigRef": { "kind": "OpenstackConfig", "name": "${MACHINE_CONFIG}" },
        "drainBeforeDelete": false, "nodeStartupTimeout": "20m"
      }],
      "upgradeStrategy": { "controlPlaneConcurrency": "1", "workerConcurrency": "10%" }
    }
  }
}
JSON

Sahkan tanpa mencipta (dryRun=All), kemudian cipta:

URL="$RANCHER_URL/apis/provisioning.cattle.io/v1/namespaces/fleet-default/clusters"
curl -sk $RES -X POST "$URL?dryRun=All" -H "Authorization: Bearer $TOK" \
  -H 'Content-Type: application/json' --data-binary @/tmp/cluster.json -o /tmp/r.json -w "HTTP=%{http_code}\n"
# HTTP=201 = sah. HTTP=422 = mesej memberitahu medan mana yang salah

curl -sk $RES -X POST "$URL" -H "Authorization: Bearer $TOK" \
  -H 'Content-Type: application/json' --data-binary @/tmp/cluster.json

Jangan guna kubectl apply untuk mencipta Cluster. Webhook admission Rancher menolaknya dengan creatorID annotation does not match user, kerana kubectl yang menuju ke API server Rancher datang sebagai sijil RKE2, bukan sebagai pengguna Rancher. Gunakan API seperti di atas. OpenstackConfig pula boleh dengan kubectl: dua laluan berbeza.

Fasa 6 dan 7: Pantau

VM dibina:

watch -n 15 'openstack server list --all-projects -c Name -c Status -f value | grep rancher'
# jangka: 3 baris, BUILD → ACTIVE (sekitar 9 minit setiap satu)

Machine dan agent:

kubectl -n fleet-default get machines \
  -o custom-columns='NAME:.metadata.name,PHASE:.status.phase' --no-headers   # Provisioning → Running

kubectl -n fleet-default get clusters.provisioning.cattle.io -o json | python3 -c "
import json,sys
for i in json.load(sys.stdin).get('items',[]):
    print('ready =', i.get('status',{}).get('ready'))
    for c in i.get('status',{}).get('conditions',[]):
        if c['type'] in ('Ready','Provisioned','Connected','Stalled'):
            print(f\"  {c['type']:<12} {c['status']:<8} {str(c.get('message') or '')[:75]}\")"

Bukti agent berjaya (kunci diagnosis): baca log konsol VM.

for VM in $(openstack server list --all-projects -c ID -c Name -f value | grep "$CLUSTER_NAME" | awk '{print $1}'); do
  openstack console log show "$VM" | grep -iE \
    "Successfully tested Rancher connection|CATTLE_SERVER was not set|Could not resolve host|FATAL" | sort -u
done

Tafsiran mesej kondisi:

MesejMaksudJika tersekat lama
waiting for infrastructure readyVM masih dibinaSemak kuota dan FIP pool
waiting for agent to check inAgent belum melaporCA, DNS atau server-url salah
waiting for cluster agent to connectAgent berjalan, RKE2 belum naikNormal, sekitar 10 minit
configuring etcd node(s)etcd sedang terbentukNormal, hampir siap
no etcd machines exist that have plansKluster tidak boleh pulihPadam dan cipta semula (Fasa 9)

Fasa 8: Sahkan siap dan ambil kubeconfig

# 8.1 kluster Ready?
kubectl -n fleet-default get clusters.provisioning.cattle.io \
  -o custom-columns='NAME:.metadata.name,READY:.status.ready,K8S:.spec.kubernetesVersion' --no-headers

# 8.2 kubeconfig kluster baharu melalui proksi Rancher
CID=$(kubectl -n fleet-default get clusters.provisioning.cattle.io "$CLUSTER_NAME" -o jsonpath='{.status.clusterName}')
curl -sk $RES -X POST "$RANCHER_URL/v3/clusters/$CID?action=generateKubeconfig" \
  -H "Authorization: Bearer $(rancher_token)" \
  | python3 -c "import json,sys; print(json.load(sys.stdin)['config'])" > ~/kubeconfig-${CLUSTER_NAME}.yaml
chmod 600 ~/kubeconfig-${CLUSTER_NAME}.yaml

# 8.3 SAHKAN node dengan kubeconfig kluster BARU, bukan kubeconfig pengurusan
KUBECONFIG=~/kubeconfig-${CLUSTER_NAME}.yaml kubectl get nodes -o wide      # jangka: 3 × Ready

Jika get nodes menyenaraikan node pengurusan Rancher, anda menggunakan kubeconfig yang salah.

8.4 Kuorum etcd. Imej etcd tiada sh, jadi jangan guna sh -c:

ETCD=$(KUBECONFIG=$KC kubectl -n kube-system get pods -o name | grep etcd | head -1)
KUBECONFIG=$KC kubectl -n kube-system exec "$ETCD" -- etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/var/lib/rancher/rke2/server/tls/etcd/server-ca.crt \
  --cert=/var/lib/rancher/rke2/server/tls/etcd/server-client.crt \
  --key=/var/lib/rancher/rke2/server/tls/etcd/server-client.key member list -w table
# jangka: 3 baris "started"
KUBECONFIG=$KC kubectl get --raw='/readyz'          # jangka: ok

Fasa 9: Buang kluster yang tersangkut

kubectl -n fleet-default delete clusters.provisioning.cattle.io "$CLUSTER_NAME" --wait=false

for i in $(seq 1 12); do
  printf "[%d] mesin=%s cp=%s kluster=%s\n" "$i" \
    "$(kubectl -n fleet-default get machines --no-headers 2>/dev/null | wc -l)" \
    "$(kubectl -n fleet-default get rkecontrolplanes --no-headers 2>/dev/null | wc -l)" \
    "$(kubectl -n fleet-default get clusters.provisioning.cattle.io --no-headers 2>/dev/null | wc -l)"
  sleep 30
done

Jangan padam rkecontrolplane sahaja. Finalizer rke-control-plane-remove menunggu machine dipadam, tetapi machinePools[].quantity: 3 menyebabkan MachineSet mencipta mesin baru secepat anda memadamnya. Ia menjadi perlumbaan dan finalizer tidak akan selesai. Padam Cluster supaya pool berhenti, dan mesin akan dibuang mengikut urutan. (Kisah penuh: Unblocking a Rancher Cluster Stuck in ‘Deleting’.)

Jika VM yatim tinggal (machine sudah tiada tetapi VM masih hidup), senaraikan dan padam yang tiada CR Machine sepadan: openstack server delete <nama-vm>.


Urutan ringkas

Fasa 0  preflight  → 5 gate: DNS · server-url · /cacerts · driver · quota/flavor
Fasa 1  token      → log masuk, simpan $TOK
Fasa 2  driver     → PUT /v3/nodedrivers/openstack {"active":true}
Fasa 3  credential → cari / guna semula
Fasa 4  machinecfg → kubectl apply nc-<nama>.yaml
Fasa 5  cluster    → curl POST /apis/.../clusters  (BUKAN kubectl)
Fasa 6  VM         → BUILD → ACTIVE ~9 min
Fasa 7  agent      → "Successfully tested Rancher connection"
Fasa 8  sahkan     → kubectl get nodes (kubeconfig BARU) + etcdctl member list
Fasa 9  (kebal)    → padam Cluster, JANGAN padam rkecontrolplane

Falsafah di sebalik semua ini: satu arahan, satu komponen, jangan lompat. Apabila kluster tersangkut, mesej kondisi pada Fasa 7 menyempitkan masalah kepada satu daripada tiga punca (CA, DNS atau server-url), dan Fasa 0 memastikan ketiga-tiganya sudah diperiksa sebelum VM pertama dibina.