本页面介绍了如何在 Google Cloud NetApp Volumes 中为 ONTAP 存储池设置对等互连。
集群对等互连和存储虚拟机 (SVM) 对等互连可将 Flex Unified ONTAP 存储池连接到另一个集群。这样一来,SnapMirror 和 FlexCache 等服务便可运行。
使用 peer_cluster.py 脚本接受方案,并为 ONTAP 模式池输入口令。如需了解详情,请参阅集群对等互连脚本。
选择程序
根据您的设置,选择以下对等互连程序之一:
混合对等互连:您从 ONTAP 模式池迁移或复制到默认模式 NetApp Volumes 卷(标准、高级、极速或灵活统一默认模式)。NetApp Volumes 会从目标位置开始对等互连。您在 ONTAP 模式源池上完成对等。
手动对等互连:您自行建立对等互连,并且未使用混合迁移或外部复制工作流。选择一个场景:
一个 ONTAP 模式池和一个位于本地或 NetApp Volumes 外部的外部 ONTAP 集群。
两个 ONTAP 模式池。
如需详细了解外部 ONTAP 集群上的其他对等互连选项,请参阅 NetApp ONTAP 文档。
ONTAP 模式池中的集群对等互连如何运作
集群对等互连流程遵循“提议和接受”工作流:
提供方:一个集群向对等 IC-LIF 地址发起对等连接,并接收口令。
接受:对等集群使用提供集群的 IC-LIF 地址和口令完成对等互连。
ONTAP CLI 代理不是交互式的。cluster peer create -generate-passphrase 命令不会提示输入,因此您可以使用 ontap_execute 在 ONTAP 模式池上运行该优惠。以英文逗号分隔的 -peer-addrs 值列出所有远程 IC-LIF 地址。cluster peer accept 命令会提示输入口令,因此该步骤会通过代理失败。当 ONTAP 模式池必须接受报价时,请使用 peer_cluster.py。
如需对等互连集群,请使用以下任一方案:
方案 A(ONTAP 模式和外部 ONTAP):使用
ontap_execute和所有对等 IC-LIF 地址在 ONTAP 模式池上运行cluster peer create-generate-passphrase。外部 ONTAP 管理员在集群提示符下运行cluster peer accept,其中包含所有 ONTAP 模式 IC-LIF 地址,然后输入口令。方案 B(两个 ONTAP 模式的池):使用
ontap_execute在池 A 上运行cluster peer create-generate-passphrase,然后使用PEER_MODE=accept在池 B 上运行peer_cluster.py。
通过在 ONTAP 模式池上使用 ontap_execute 运行 vserver peer accept 来执行 SVM 对等互连。在方案 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 控制台的“复制”或“迁移”标签页中,或使用 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 peer 命令。使用 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 位于 IP 空间
Gcnv中;在外部 ONTAP 集群上,它们位于 IP 空间Default中。测试 ICMP 和所需的集群间端口。请在开始之前设置 ontap_execute。
收集 ONTAP 模式池中的集群名称、SVM 名称和所有对等集群 IC-LIF IP 地址。每个节点提供一个地址,并以英文逗号分隔。IC-LIF 的数量与对等集群节点数量一致。
与对等集群管理员分享您的池的集群名称、SVM 名称和所有 IC-LIF 地址。
概览
如需手动对等互连集群和 SVM,请执行以下步骤:
查找名称和 IC-LIF
在每个 ONTAP 模式池上,运行常见 ONTAP 模式程序中所述的查找命令。
列出 IC-LIF 地址(取决于池大小),并将其提供给对等集群管理员。
验证连接性
使用 -vserver Gcnv 标志运行 ping 命令,以从池中对等 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),了解 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_cluster.py 和 PEER_MODE=offer。如需了解详情,请参阅集群对等互连脚本。
复制口令并将其发送给外部 ONTAP 管理员。在口令有效期内(默认为 1 小时)接受邀请。
在对等集群提示中列出所有 ONTAP 模式 IC-LIF 地址,以接受外部 ONTAP 集群上的提议:
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_MODE=accept,并在PEER_IC_LIF_IPS中列出池 A 中的所有 IC-LIF 地址。
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
在 PEER_IC_LIF_IPS 中列出池 A 中的每个 IC-LIF。
验证每个池中的配置:
ontap_execute "cluster peer show"
确认可用性为“可用”且身份验证正常。
对等 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(如果创建命令不可用,则使用peer_svm.py)运行vserver peer create命令。在池 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"
确认集群对等可用性为“可用”,并且 SVM 对等状态为“已对等”。
对于方案 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 代理不支持删除集群对等互联。如需删除集群对等互连,请通过 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。当 ONTAP 模式池必须接受集群对等互连提议 (PEER_MODE=accept) 时,请在该池上使用此模式。混合对等互连和方案 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 代理使用 vserver peer create 时,才使用 peer_svm.py。请改用 vserver peer create 和 vserver peer accept,如对等 SVM 中所述。
#!/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 卷。