将 ONTAP 模式池加入 Active Directory

本页面介绍如何将 ONTAP 模式池加入 Active Directory 域。

ONTAP 模式下的 Flex Unified 存储池不使用 Active Directory 政策。如需将 Active Directory 与 SMB 卷搭配使用,请通过创建 CIFS 服务器将池中的 ONTAP 存储虚拟机 (SVM) 加入 Windows 域。

如需了解 Active Directory 先决条件和防火墙规则,请参阅 Google Cloud NetApp Volumes Active Directory 集成。

准备工作

完成以下先决条件:

  1. 确保存储池网络可以连接到网域控制器和 DNS 服务器。

  2. 配置 Cloud DNS,以将 Windows 域的 DNS 请求转发到 Windows DNS 服务器。

  3. 完成准备工作中的先决条件。

  4. 收集以下信息:

    1. AD_FQDN:完全限定域名,例如 corp.example.com。

    2. AD_DNS_SERVERS:Active Directory DNS 服务器的一到三个以逗号分隔的 IPv4 地址。

    3. AD_USERNAME 和 AD_PASSWORD:可以创建计算机账号的网域账号,其 SAM 账号名称不超过 20 个字符。

    4. COMPUTER_NAME:网域中计算机账号的唯一 NetBIOS 名称,最多 15 个字符。

    5. OU:可选:目标组织部门 (OU) 的标识名,例如 OU=Computers,DC=corp,DC=example,DC=com。

概览

如需将 ONTAP 模式池加入 Active Directory 域,请完成以下步骤:

  1. 设置 ontap_execute

  2. 查找池资源

  3. 在 SVM 上配置 DNS

  4. 加入网域

  5. 配置 SMB 访问权限

  6. 验证网域加入情况

查找池资源

查找存储池的以下资源:

SVM 名称

如需查找 SVM 名称,请执行以下操作:

 ontap_execute "vserver show -fields vserver,operational-state"

记下 Vserver 名称,例如 gcnv-7cf6ee41c1a94f0-svm-01。在后续步骤中,请将此名称用作 SVM_NAME。

数据 LIF

在加入网域之前,请验证数据 LIF 是否可以成功访问 DNS 服务器和网域控制器。

 ontap_execute "network interface show -role data"

如需测试与 DNS 服务器或网域控制器的连接,请使用带有 -lif 标志的 SAN LIF。ONTAP 模式池会公开名为 SVM_NAME-san-1、SVM_NAME-san-2、 的 SAN LIF,并根据需要公开其他 LIF:

 ontap_execute "network ping -vserver SVM_NAME -destination DNS_OR_DC_IP -lif SVM_NAME-san-1"

如果 ping 失败,则存储池无法通过网络连接到 DNS 服务器或网域控制器。VPC 中的虚拟机 (VM) 可能仍然能够连接到这些主机。如需详细了解如何排查 连接问题,请参阅为什么 NetApp Volumes 无法连接到 Active Directory 和 VPC 后面的其他网络。

在 SVM 上配置 DNS

如需在 SVM 上配置 DNS,请执行以下操作:

 ontap_execute "vserver services name-service dns create -vserver SVM_NAME -domains AD_FQDN -name-servers AD_DNS_SERVERS"

如果主机名称服务开关不包含 DNS,请添加它:

 ontap_execute "vserver services name-service ns-switch modify -vserver SVM_NAME -database hosts -sources dns,files"

验证配置:

 ontap_execute "vserver services name-service dns show -vserver SVM_NAME"

加入网域

本部分介绍 ONTAP CLI 命令、CLI 代理的限制以及如何使用 Python 加入网域。

ONTAP CLI 命令

在具有直接 ONTAP CLI 访问权限的系统上,运行以下命令:

 vserver cifs create -vserver SVM_NAME -cifs-server COMPUTER_NAME -domain AD_FQDN

如需添加可选参数,请使用 -ou OU 和 -default-site SITE。ONTAP 会提示您输入网域密码,并且该命令可能需要几分钟时间。

CLI 代理的限制

ONTAP CLI 代理不是交互式的。由于 vserver cifs create 命令会提示输入密码,因此您无法通过代理完成加入网域的操作。 请改用 REST API。API 等效项为 POST /protocols/cifs/services。

该脚本仅针对与 Active Directory 的 Kerberos 通信宣传 AES-256。这是现代网域的推荐设置。如需同时宣传 AES-128 或 DES,请在运行脚本之前向脚本中的 advertised_kdc_encryptions 数组添加值,或者在加入网域后使用 vserver cifs security modify -advertised-enc-types 修改 CIFS 服务器。不建议使用 DES,因为它使用弱加密。

有效的加密类型值为 aes-256、aes-128、rc4 和 des。如果在加入网域后更改宣传的加密类型,ONTAP 会重置机器账号密码并提示输入 Active Directory 凭据。

使用 Python 加入网域

将以下脚本另存为 join_ad.py,并使用 Python 3.8 或更高版本运行该脚本。 该脚本仅使用 Python 标准库。

 #!/usr/bin/env python3
"""Join an ONTAP-mode SVM to Active Directory by creating a CIFS server."""

import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request

# Required environment variables.
REQUIRED = (
    "PROJECT_ID", "LOCATION", "POOL_NAME", "SVM_NAME",
    "AD_FQDN", "COMPUTER_NAME", "AD_USERNAME", "AD_PASSWORD",
)
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"]
SVM_NAME = os.environ["SVM_NAME"]
AD_FQDN = os.environ["AD_FQDN"]
COMPUTER_NAME = os.environ["COMPUTER_NAME"]
AD_USERNAME = os.environ["AD_USERNAME"]
AD_PASSWORD = os.environ["AD_PASSWORD"]

# Optional environment variables.
OU = os.environ.get("OU", "")
AD_SITE = os.environ.get("AD_SITE", "")
POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL", "10"))

# Kerberos encryption types advertised to Active Directory. AES-256 only by default.
# To add aes-128 or des (not recommended), extend this list before you run the script.
ADVERTISED_KDC_ENCRYPTIONS = ["aes-256"]

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:
        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":
            print("Domain join completed.")
            return
        if state in ("failure", "error"):
            sys.exit(f"Job failed: {format_job_error(response)}")
        time.sleep(POLL_INTERVAL)

def main() -> None:
    ad_domain = {
        "fqdn": AD_FQDN,
        "user": AD_USERNAME,
        "password": AD_PASSWORD,
    }
    if OU:
        ad_domain["organizational_unit"] = OU
    if AD_SITE:
        ad_domain["default_site"] = AD_SITE

    body = {
        "body": {
            "svm": {"name": SVM_NAME},
            "name": COMPUTER_NAME,
            "ad_domain": ad_domain,
            "security": {
                "advertised_kdc_encryptions": ADVERTISED_KDC_ENCRYPTIONS,
            },
        }
    }

    print(f"Creating CIFS server and joining {AD_FQDN}...")
    response = api_request("POST", "/protocols/cifs/services?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()

配置 SMB 访问权限

向网域用户或群组授予本地管理员权限:

 ontap_execute "vserver cifs users-and-groups local-group add-members -vserver SVM_NAME -group-name BUILTIN\\Administrators -member-names NETBIOS_DOMAIN\\username"

将 NETBIOS_DOMAIN 替换为 NetBIOS 域名,而不是 DNS 名称。

如果您使用与 Active Directory 集成的 DNS,请启用安全动态 DNS 更新。 您必须将 -vserver-fqdn 标志设置为 CIFS 服务器 FQDN,例如 COMPUTER_NAME.AD_FQDN:

 ontap_execute "vserver services name-service dns dynamic-update modify -vserver SVM_NAME -is-enabled true -use-secure true -vserver-fqdn COMPUTER_NAME.AD_FQDN"

验证网域加入情况

如需验证网域加入情况,请执行以下操作:

 ontap_execute "vserver cifs show -vserver SVM_NAME"

确认 CIFS 服务器处于在线状态并已加入预期网域。

移除 CIFS 服务器

如需删除 CIFS 服务器并重新加入网域,请完成以下步骤:

  1. 停用安全动态 DNS 更新:

     ontap_execute "vserver services name-service dns dynamic-update modify -vserver SVM_NAME -is-enabled false -use-secure false"
    

    在启用安全 DDNS 时,ONTAP 会阻止删除 CIFS。

  2. 从 Active Directory 中移除计算机账号,或使用有权在目标 OU 中删除计算机账号的网域账号。

  3. 在 ONTAP 上删除 CIFS 服务器:

     ontap_execute "vserver cifs delete -vserver SVM_NAME -skip-ad-account-delete true"
    

    如果计算机账号已从 Active Directory 中移除,或者用于删除的账号没有移除计算机对象的权限,请使用 -skip-ad-account-delete true。在这种情况下,ONTAP 仅移除本地 CIFS 配置,而不会尝试删除 Active Directory 对象。

    默认情况下,vserver cifs delete 还会从 Active Directory 中移除计算机账号,并提示输入网域管理员凭据。此提示无法通过 ONTAP CLI 代理运行。

后续步骤