本頁說明如何為 Google Cloud NetApp Volumes 中的 ONTAP 儲存集區設定對等互連。
叢集對等互連和儲存虛擬機器 (SVM) 對等互連,可將 FlexUnified ONTAP 儲存集區連線至其他叢集。這項功能可讓 SnapMirror 和 FlexCache 等服務運作。
使用 peer_cluster.py 指令碼接受提案,並輸入 ONTAP 模式集區的通關密語。詳情請參閱叢集對等互連指令碼。
選擇程序
請根據您的設定,選擇下列其中一種對等互連程序:
混合對等互連:從 ONTAP 模式集區遷移或複製到 Default 模式的 NetApp Volumes 磁碟區 (Standard、Premium、Extreme 或 Flex Unified Default 模式)。NetApp Volumes 會從目的地開始對接。在 ONTAP 模式來源集區完成對等互連。
手動對等互連:您自行建立對等互連,且未使用混合式遷移或外部複製工作流程。選取情境:
ONTAP 模式集區,以及位於地端部署環境或 NetApp Volumes 外部的外部 ONTAP 叢集。
兩個 ONTAP 模式集區。
如要進一步瞭解外部 ONTAP 叢集上的其他對等互連選項,請參閱 NetApp ONTAP 說明文件。
ONTAP 模式集區的叢集對等互連運作方式
叢集對等互連程序會遵循「提供」和「接受」工作流程:
提供:一個叢集會朝對等互連 IC-LIF 位址啟動對等互連,並接收密碼片語。
接受:對等互連叢集使用提供叢集的 IC-LIF 位址和通關密語,完成對等互連。
ONTAP CLI Proxy 並非互動式,cluster peer create -generate-passphrase 指令不會提示輸入內容,因此您可以使用 ontap_execute 在 ONTAP 模式集區上執行方案。以半形逗號分隔的 -peer-addrs 值列出所有遠端 IC-LIF 位址。cluster peer accept 指令會提示輸入密碼片語,因此該步驟會透過 Proxy 失敗。當 ONTAP 模式集區必須接受供應項目時,請使用 peer_cluster.py。
如要對等互連叢集,請使用下列其中一個情境:
情境 A (ONTAP 模式和外部 ONTAP):在 ONTAP 模式集區中執行
cluster peer create-generate-passphrase,並搭配使用ontap_execute和所有對等 IC-LIF 位址。外部 ONTAP 管理員會在叢集提示中執行cluster peer accept,並輸入所有 ONTAP 模式的 IC-LIF 位址和密碼。情境 B (兩個 ONTAP 模式集區):使用
ontap_execute在集區 A 上執行cluster peer create-generate-passphrase,然後在集區 B 上執行peer_cluster.py(使用PEER_MODE=accept)。
在 ONTAP 模式集區中執行 ontap_execute,藉此執行 SVM 對等互連 vserver peer accept。在情境 A 中,外部 ONTAP 叢集會執行 vserver peer create。
需要對等互連的複製方法
下列複製方法需要對等互連:
任何 ONTAP 來源到 Flex Unified ONTAP 模式 (SnapMirror):在 ONTAP 模式集區上對等互連。
將 Flex Unified ONTAP 模式對等至任何 ONTAP 目的地 (SnapMirror):在 ONTAP 模式集區上對等。
ONTAP 模式來源到預設模式 NetApp Volumes (磁碟區遷移或外部複製):在 ONTAP 模式來源集區上使用混合對等互連。
NetApp Volumes 內的預設模式磁碟區複製作業不會使用 ONTAP 叢集對等互連。
混合對等互連
如果來源是 ONTAP 模式集區,目的地是 NetApp Volumes 中的預設模式磁碟區,請使用本節內容。
NetApp Volumes 的運作方式
混合對等互連工作流程包含下列步驟:
您可以在 Google Cloud 控制台中,或使用 Google Cloud CLI,在目的地磁碟區上建立磁碟區遷移或外部複製作業。您指定 ONTAP 模式的來源叢集名稱、SVM、磁碟區和來源 IC-LIF 位址。
如果尚未建立叢集對等互連,NetApp Volumes 會檢查目的地連線能力,產生通關密語,並顯示對等互連操作說明。
您必須在 ONTAP 模式的來源集區中執行其餘對等互連步驟,因為 NetApp Volumes 不會在來源集區中執行自動化指令。
這項操作無法復原。您必須先建立「預設模式」目的地。
在 ONTAP 模式來源完成叢集對接
在 Google Cloud 控制台的「Replication」或「Migration」分頁中,或使用 Google Cloud CLI 擷取密碼片語和目的地 IC-LIF 位址:
gcloud netapp volumes replications list --volume=DESTINATION_VOLUME --location=LOCATION \ --format="table(hybridPeeringDetails.command,hybridPeeringDetails.passphrase,hybridPeeringDetails.subnetIp)"
在 ONTAP 模式來源集區中,接受 NetApp Volumes 在目的地提供的對等互連。如常見 ONTAP 模式程序所述,設定 ontap_execute。為來源集區設定 POOL_NAME 和 LOCATION。在 PEER_IC_LIF_IPS 中列出所有目的地 IC-LIF 位址,然後執行 peer_cluster.py (使用 PEER_MODE=accept):
export PROJECT_ID="myproject" export LOCATION="us-central1-a" export POOL_NAME="ontap-mode-source-pool" export PEER_MODE="accept" export PEER_IC_LIF_IPS="DESTINATION_IC_LIF_IP_1,DESTINATION_IC_LIF_IP_2" export PASSPHRASE="passphrase-from-gcnv-console" python3 peer_cluster.py
驗證來源集區的設定:
ontap_execute "cluster peer show"
在 ONTAP 模式來源上完成 SVM 對等互連
如果 NetApp Volumes 未自動完成 SVM 對等互連,系統會顯示 vserver 對等互連指令。使用 ontap_execute 執行指令,或接受待處理的對等互連:
ontap_execute "vserver peer accept -vserver SOURCE_SVM_NAME -peer-vserver PEER_SVM_NAME"
驗證設定:
ontap_execute "vserver peer show"
返回 Google Cloud 控制台中的混合式工作流程,或執行 gcloud netapp volumes replications list,確認複製或遷移作業正在進行中。
手動對等互連
如果您要自行建立對等互連,且不使用混合式工作流程,例如在 ONTAP 模式集區與外部 ONTAP 叢集之間,或在兩個 ONTAP 模式集區之間使用 SnapMirror 之前,請使用本節內容。
事前準備
完成下列必要條件:
確認 ONTAP 模式集區的 IC-LIF 可連上對等叢集的 IC-LIF。 ONTAP 模式集區的 IC-LIF 位於 IPspace
Gcnv,外部 ONTAP 叢集的 IC-LIF 則位於 IPspaceDefault。測試 ICMP 和叢集間所需的通訊埠。開始前,請先設定 ontap_execute。
在 ONTAP 模式集區中,收集叢集名稱、SVM 名稱和所有對等叢集 IC-LIF IP 位址。請以半形逗號分隔各節點的地址。IC-LIF 數量與對等叢集節點數量相符。
將集區的叢集名稱、SVM 名稱和所有 IC-LIF 位址分享給對等叢集管理員。
總覽
如要手動對等互連叢集和 SVM,請執行下列步驟:
查詢名稱和 IC-LIF
在每個 ONTAP 模式集區中,執行「常見 ONTAP 模式程序」一文所述的查閱指令。
列出 IC-LIF 位址 (取決於集區大小),並提供給對等叢集管理員。
確認連線
使用 -vserver Gcnv 旗標,從集區對等互連 IC-LIF 執行 ping 指令:
ontap_execute "network ping -vserver Gcnv -destination PEER_IC_LIF_IP -lif intercluster-1"
請先修正路由或防火牆問題再繼續。如果集區之間無法執行 Ping,請參閱「Why your NetApp Volumes can't reach Active Directory (and other networks behind your VPC)」,瞭解虛擬私有雲和合作夥伴網路的相關指引。
對等互連叢集
如要對等互連叢集,請使用下列其中一個情境:
情境 A:ONTAP 模式集區和外部 ONTAP
如要將 ONTAP 模式集區與外部 ONTAP 叢集對等互連,請執行下列步驟:
ONTAP 模式集區的優惠:使用
ontap_execute搭配所有對等 IC-LIF 位址,執行cluster peer create -generate-passphrase指令。從指令輸出內容複製密碼片語。在外部 ONTAP 上接受:對等叢集管理員在 ONTAP 提示字元中執行
cluster peer accept指令,並輸入所有 ONTAP 模式 IC-LIF 位址和密碼。
如要在 ONTAP 模式集區提供對等互連:
ontap_execute "cluster peer create -ipspace Gcnv -peer-addrs PEER_IC_LIF_1,PEER_IC_LIF_2 -generate-passphrase"
在 -peer-addrs 旗標中列出對等叢集的每個 IC-LIF 位址,並以半形逗號分隔。如果對等互連叢集有超過兩個節點,請新增其他位址。
ONTAP 會在指令輸出內容的 Passphrase: 下方顯示密碼片語。立即複製密碼片語;ONTAP 會警告您無法再次顯示密碼片語。
如果指令失敗或輸出內容未包含密碼片語,請改用 PEER_MODE=offer 執行 peer_cluster.py。詳情請參閱「叢集對等互連指令碼」。
複製密語並傳送給外部 ONTAP 管理員。在通關密語有效期限內接受邀請,預設為一小時。
在對等叢集提示中接受外部 ONTAP 叢集的提議,方法是列出所有 ONTAP 模式 IC-LIF 位址:
cluster peer accept -ipspace Default -peer-addrs ONTAP_MODE_IC_LIF_1,ONTAP_MODE_IC_LIF_2
ONTAP 提示時,請輸入通關密語。請納入 ONTAP 模式集區中的所有 IC-LIF 位址。
驗證 ONTAP 模式集區的設定:
ontap_execute "cluster peer show"
情境 B:兩個 ONTAP 模式集區
如要對等互連兩個 ONTAP 模式集區,請執行下列步驟:
在集區 A 上提供:使用集區 A 上的
ontap_execute執行cluster peer create -generate-passphrase指令。在-peer-addrs中列出集區 B 的所有 IC-LIF 位址。複製通關密語。在集區 B 上接受:在集區 B 上執行
peer_cluster.py,並在PEER_IC_LIF_IPS中列出集區 A 的所有 IC-LIF 位址。PEER_MODE=accept
A 組優惠:
ontap_execute "cluster peer create -ipspace Gcnv -peer-addrs POOL_B_IC_LIF_1,POOL_B_IC_LIF_2 -generate-passphrase"
列出集區 B 中的每個 IC-LIF,並以半形逗號分隔。如果集區 B 有兩個以上的節點,請新增其他位址。從指令輸出內容複製 Passphrase: 值。
如果指令失敗,請在集區 A 中使用 peer_cluster.py 和 PEER_MODE=offer。
在集區 B 中接受:
export PROJECT_ID="myproject" export LOCATION="us-west1-b" export POOL_NAME="pool-b" export PEER_MODE="accept" export PEER_IC_LIF_IPS="POOL_A_IC_LIF_1,POOL_A_IC_LIF_2" export PASSPHRASE="passphrase-from-pool-a" python3 peer_cluster.py
列出集區 A 中的每個 IC-LIF,如 PEER_IC_LIF_IPS 所示。
驗證每個集區的設定:
ontap_execute "cluster peer show"
確認「可用性」為「可用」,且驗證狀態為「OK」。
對等互連 SVM
叢集對等互連成功後,請為 SnapMirror 建立 SVM 對等互連。如需 FlexCache,請將其新增至 -applications 旗標。
情境 A:ONTAP 模式集區和外部 ONTAP
如要將 ONTAP 模式集區與外部 ONTAP 叢集對等互連,請執行下列步驟:
在外部 ONTAP 上建立:對等叢集管理員會在 ONTAP 提示中執行
vserver peer create指令。在 ONTAP 模式集區中接受:使用
ontap_execute執行vserver peer accept指令。
在外部 ONTAP 叢集的對等互連叢集提示中建立對等互連:
vserver peer create -vserver PEER_SVM_NAME -peer-vserver ONTAP_MODE_SVM_NAME \ -peer-cluster ONTAP_MODE_CLUSTER_NAME -applications snapmirror
在 ONTAP 模式集區中接受對等互連:
ontap_execute "vserver peer accept -vserver ONTAP_MODE_SVM_NAME -peer-vserver PEER_SVM_NAME"
情境 B:兩個 ONTAP 模式集區
如要對等互連兩個 ONTAP 模式集區,請執行下列步驟:
在集區 A 上建立:在集區 A 上使用
ontap_execute執行vserver peer create指令 (如果無法使用建立指令,請使用peer_svm.py)。在集區 B 上接受:在集區 B 上使用
ontap_execute執行vserver peer accept指令。
在集區 A 上建立對等互連:
ontap_execute "vserver peer create -vserver POOL_A_SVM -peer-vserver POOL_B_SVM -peer-cluster POOL_B_CLUSTER_NAME -applications snapmirror"
在集區 B 上接受對等互連:
ontap_execute "vserver peer accept -vserver POOL_B_SVM -peer-vserver POOL_A_SVM"
請勿在兩個集區中建立相同的 SVM 對等互連。
驗證對等互連
如要驗證 ONTAP 模式集區的對等互連:
ontap_execute "cluster peer show" ontap_execute "vserver peer show"
確認叢集對等互連可用性為「Available」,且 SVM 對等互連狀態為「peered」。
如果是情境 B,請在第二個集區重複上述程序。外部 ONTAP 管理員可以在對等互連叢集上執行 cluster peer show 和 vserver peer show,驗證設定。
如果沒有叢集對等互連,cluster peer show 指令會傳回項目不存在。
刪除對等互連
請先刪除 SVM 對等互連,再刪除叢集對等互連。
如要刪除一個集區的 SVM 對等互連,請刪除該對等互連,以便在集區可連線時,移除雙方的關係。
ontap_execute "vserver peer delete -vserver LOCAL_SVM_NAME -peer-vserver PEER_SVM_NAME"
ONTAP CLI Proxy 不支援刪除叢集對等互連。如要刪除叢集對等互連,請透過 REST API 依 UUID 刪除。
首先,列出對等互連項目,找出 UUID:
TOKEN=$(gcloud auth print-access-token) curl -s -H "Authorization: Bearer $TOKEN" \ "https://netapp.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/storagePools/POOL_NAME/ontap/api/cluster/peers"
刪除每個傳回的 UUID:
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \ "https://netapp.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/storagePools/POOL_NAME/ontap/api/cluster/peers/PEER_UUID"
在情境 B 中,對兩個 ONTAP 模式集區重複執行這個程序。如為情境 A,請視需要對外部 ONTAP 叢集執行 cluster peer delete。
叢集對等互連指令碼
將下列內容儲存為 peer_cluster.py。如果集區必須接受叢集對等互連邀請 (PEER_MODE=accept),請在 ONTAP 模式集區中使用。混合式對等互連和情境 B 使用 accept 模式。只有在 cluster peer create -generate-passphrase 透過 ontap_execute 失敗或未傳回通關密語時,才使用 offer 模式 (PEER_MODE=offer)。這項指令碼需要 Python 3.8 以上版本和 Python 標準程式庫。
#!/usr/bin/env python3
"""Cluster peer offer or accept on an ONTAP-mode pool through the NetApp Volumes REST proxy."""
import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request
REQUIRED = ("PROJECT_ID", "LOCATION", "POOL_NAME", "PEER_MODE")
missing = [name for name in REQUIRED if not os.environ.get(name)]
if missing:
sys.exit(f"Set required environment variables: {', '.join(missing)}")
PROJECT_ID = os.environ["PROJECT_ID"]
LOCATION = os.environ["LOCATION"]
POOL_NAME = os.environ["POOL_NAME"]
PEER_MODE = os.environ["PEER_MODE"].lower()
POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL", "10"))
API_ROOT = (
f"https://netapp.googleapis.com/v1/projects/{PROJECT_ID}"
f"/locations/{LOCATION}/storagePools/{POOL_NAME}/ontap/api"
)
def parse_ip_list(value: str) -> list:
return [ip.strip() for ip in value.replace("#", ",").split(",") if ip.strip()]
def get_access_token() -> str:
return subprocess.check_output(
["gcloud", "auth", "print-access-token"], text=True
).strip()
def api_request(method: str, path: str, body=None) -> dict:
headers = {
"Authorization": f"Bearer {get_access_token()}",
"Content-Type": "application/json",
}
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(f"{API_ROOT}{path}", data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())
except urllib.error.HTTPError as exc:
detail = exc.read().decode()
sys.exit(f"API request failed ({exc.code}): {detail}")
def job_record(response: dict) -> dict:
return response.get("rawResponse") or response.get("body") or response
def job_state(response: dict) -> str:
return job_record(response).get("state", "")
def format_job_error(response: dict) -> str:
record = job_record(response)
message = record.get("message") or record.get("error", {}).get("message")
if message:
return message
return json.dumps(response, indent=2)
def wait_for_job(job_uuid: str) -> None:
print(f"ONTAP job UUID: {job_uuid}")
while True:
response = api_request("GET", f"/cluster/jobs/{job_uuid}")
state = job_state(response)
print(f" Job state: {state}")
if state == "success":
return
if state in ("failure", "error"):
sys.exit(f"Job failed: {format_job_error(response)}")
time.sleep(POLL_INTERVAL)
def extract_passphrase(response: dict) -> str:
body = response.get("body") or response.get("rawResponse") or {}
records = body.get("records") or []
if records:
auth = records[0].get("authentication") or {}
if auth.get("passphrase"):
return auth["passphrase"]
auth = body.get("authentication") or {}
return auth.get("passphrase", "")
def build_offer_body() -> dict:
if not os.environ.get("PEER_IC_LIF_IPS"):
sys.exit("Set PEER_IC_LIF_IPS for offer mode")
payload = {
"remote": {"ip_addresses": parse_ip_list(os.environ["PEER_IC_LIF_IPS"])},
"authentication": {"generate_passphrase": True},
"ipspace": {"name": os.environ.get("IPSPACE", "Gcnv")},
}
expiry = os.environ.get("EXPIRY_TIME")
if expiry:
payload["authentication"]["expiry_time"] = expiry
local_svm = os.environ.get("LOCAL_SVM_NAME")
if local_svm:
payload["initial_allowed_svms"] = [{"name": local_svm}]
apps = os.environ.get("PEER_APPLICATIONS", "snapmirror")
payload["peer_applications"] = [a.strip() for a in apps.split(",") if a.strip()]
encryption = os.environ.get("ENCRYPTION_PROPOSED")
if encryption:
payload["encryption"] = {"proposed": encryption}
return {"body": payload}
def build_accept_body() -> dict:
for name in ("PEER_IC_LIF_IPS", "PASSPHRASE"):
if not os.environ.get(name):
sys.exit(f"Set {name} for accept mode")
return {
"body": {
"remote": {"ip_addresses": parse_ip_list(os.environ["PEER_IC_LIF_IPS"])},
"authentication": {"passphrase": os.environ["PASSPHRASE"]},
"ipspace": {"name": os.environ.get("IPSPACE", "Gcnv")},
}
}
def main() -> None:
if PEER_MODE == "offer":
body = build_offer_body()
print("Creating cluster peer offer...")
elif PEER_MODE == "accept":
body = build_accept_body()
print("Accepting cluster peer offer...")
else:
sys.exit("PEER_MODE must be 'offer' or 'accept'")
response = api_request("POST", "/cluster/peers?return_records=true", body)
job = (response.get("body") or {}).get("job") or response.get("job")
if job and job.get("uuid"):
wait_for_job(job["uuid"])
if PEER_MODE == "offer":
passphrase = extract_passphrase(response)
if passphrase:
print(f"\nPassphrase (provide to the peer pool): {passphrase}")
else:
print(json.dumps(response, indent=2))
if __name__ == "__main__":
main()
選用環境變數:EXPIRY_TIME、PEER_APPLICATIONS (預設值 snapmirror)、ENCRYPTION_PROPOSED 和 IPSPACE (預設值 Gcnv)。
SVM 對等互連指令碼 (備用)
只有在無法透過 ONTAP CLI Proxy 使用 vserver peer create 時,才使用 peer_svm.py。請改用對等 SVM 中所述的 vserver peer create 和 vserver peer accept。
#!/usr/bin/env python3
"""Create an SVM peer on an ONTAP-mode pool (fallback when vserver peer create is unavailable)."""
import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request
REQUIRED = (
"PROJECT_ID", "LOCATION", "POOL_NAME",
"LOCAL_SVM_NAME", "PEER_CLUSTER_NAME", "PEER_SVM_NAME",
)
missing = [name for name in REQUIRED if not os.environ.get(name)]
if missing:
sys.exit(f"Set required environment variables: {', '.join(missing)}")
PROJECT_ID = os.environ["PROJECT_ID"]
LOCATION = os.environ["LOCATION"]
POOL_NAME = os.environ["POOL_NAME"]
LOCAL_SVM_NAME = os.environ["LOCAL_SVM_NAME"]
PEER_CLUSTER_NAME = os.environ["PEER_CLUSTER_NAME"]
PEER_SVM_NAME = os.environ["PEER_SVM_NAME"]
APPLICATIONS = [
app.strip()
for app in os.environ.get("APPLICATIONS", "snapmirror").split(",")
if app.strip()
]
POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL", "10"))
API_ROOT = (
f"https://netapp.googleapis.com/v1/projects/{PROJECT_ID}"
f"/locations/{LOCATION}/storagePools/{POOL_NAME}/ontap/api"
)
def get_access_token() -> str:
return subprocess.check_output(
["gcloud", "auth", "print-access-token"], text=True
).strip()
def api_request(method: str, path: str, body=None) -> dict:
headers = {
"Authorization": f"Bearer {get_access_token()}",
"Content-Type": "application/json",
}
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(f"{API_ROOT}{path}", data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())
except urllib.error.HTTPError as exc:
sys.exit(f"API request failed ({exc.code}): {exc.read().decode()}")
def format_job_error(response: dict) -> str:
record = response.get("rawResponse") or response.get("body") or response
message = record.get("message") or record.get("error", {}).get("message")
return message or json.dumps(response, indent=2)
def wait_for_job(job_uuid: str) -> None:
while True:
response = api_request("GET", f"/cluster/jobs/{job_uuid}")
record = response.get("rawResponse") or response.get("body") or response
state = record.get("state", "")
print(f" Job state: {state}")
if state == "success":
print("SVM peer relationship created.")
return
if state in ("failure", "error"):
sys.exit(f"Job failed: {format_job_error(response)}")
time.sleep(POLL_INTERVAL)
def main() -> None:
body = {
"body": {
"svm": {"name": LOCAL_SVM_NAME},
"peer": {
"cluster": {"name": PEER_CLUSTER_NAME},
"svm": {"name": PEER_SVM_NAME},
},
"applications": APPLICATIONS,
}
}
response = api_request("POST", "/svm/peers?return_records=true", body)
job = (response.get("body") or {}).get("job") or response.get("job")
if not job or not job.get("uuid"):
print(json.dumps(response, indent=2))
return
wait_for_job(job["uuid"])
if __name__ == "__main__":
main()
後續步驟
在 ONTAP 模式集區中,使用
snapmirror create和snapmirror initialize建立 SnapMirror 關係。詳情請參閱「SnapMirror」。與 FlexCache 應用程式對等互連後,建立 FlexCache 磁碟區。