將 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:以半形逗號分隔的一到三個 IPv4 位址,適用於 Active Directory DNS 伺服器。

    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 伺服器或網域控制器的連線,請使用 SAN LIF 並加上 -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 伺服器或網域控制站。虛擬私有雲中的虛擬機器 (VM) 可能仍可連線至這些主機。如要進一步瞭解如何排解連線問題,請參閱「Why your NetApp Volumes can't connect to Active Directory and other networks behind your 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 Proxy 的限制,以及如何使用 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 Proxy 的限制

ONTAP CLI Proxy 並非互動式,由於 vserver cifs create 命令會提示輸入密碼,因此您無法透過 Proxy 完成加入網域的程序。請改用 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 Proxy 運作。

後續步驟