충돌 진단을 사용하여 워크로드 클러스터 문제 해결

이 항목에서는 충돌 진단(Crashd)을 사용하여 독립형 관리 클러스터가 있는 Tanzu Kubernetes Grid의 Photon OS 기반으로 불안정하거나 응답하지 않는 워크로드 클러스터를 진단하는 방법을 설명합니다.

Crashd를 사용하여 vSphere with Tanzu Supervisor에서 배포된 워크로드 클러스터를 진단하는 방법은 VMware 기술 자료의 vSphere with TanzuvSphere with Tanzu의 Tanzu Kubernetes 게스트 클러스터에서 진단 로그 번들을 수집하는 방법을 참조하십시오.

개요: Crashd

Crashd는 클러스터를 클라우드 인프라에 배포하기 전에 tanzu cluster create 프로세스에서 kind를 사용하여 로컬로 생성하는 부트스트랩 워크로드 클러스터를 검토합니다.

Crashd는 Kubernetes 클러스터의 문제를 쉽게 해결할 수 있는 오픈 소스 프로젝트입니다.

Crashd는 Python과 같은 언어인 Starlark로 작성된 스크립트 파일을 사용하여 관리 또는 워크로드 클러스터와 상호 작용하여 인프라와 클러스터 정보를 수집합니다.

Crashd는 다음을 포함하여 지원되는 인프라에서 진단을 수집할 수 있습니다.

  • AWS
  • Azure
  • vSphere

Crashd는 스크립트가 실행하는 명령에서 출력을 가져와 출력을 tar 파일에 추가합니다. 그런 다음 추가 분석을 위해 tar 파일이 로컬로 저장됩니다.

Tanzu Kubernetes Grid에는 Crashd를 위한 서명된 바이너리 및 Photon OS 워크로드 클러스터용 진단 스크립트 파일이 포함되어 있습니다.

Crashd 바이너리 설치 또는 업그레이드

crashd를 설치하거나 업그레이드하려면 아래 지침을 따르십시오.

  1. Tanzu Kubernetes Grid 다운로드 페이지로 이동한 후 VMware Customer Connect 자격 증명으로 로그인합니다.
  2. 해당 플랫폼용 Crashd를 다운로드합니다.

    • Linux: crashd-linux-amd64-v0.3.7+vmware.5-4-g59b239d.tar.gz
    • macOS: crashd-darwin-amd64-v0.3.7+vmware.5-4-g59b239d.tar.gz
  3. tar 명령을 사용하여 플랫폼의 바이너리 압축을 풉니다.

    • Linux:

      tar -xvf crashd-linux-amd64-v0.3.7-vmware.6.tar.gz
      
    • macOS:

      tar -xvf crashd-darwin-amd64-v0.3.7-vmware.6.tar.gz
      
  4. 이전 단계에서 다음 파일이 있는 crashd 디렉토리가 생성됩니다.

    crashd
    crashd/args
    crashd/diagnostics.crsh
    crashd/crashd-PLATFORM-amd64-v0.3.7+vmware.6
    
  5. 바이너리를 /usr/local/bin 폴더로 이동합니다.

    • Linux:

      mv ./crashd/crashd-linux-amd64-v0.3.7+vmware.6 /usr/local/bin/crashd
      
    • macOS:

      mv ./crashd/crashd-darwin-amd64-v0.3.7+vmware.6 /usr/local/bin/crashd
      

Photon OS 워크로드 클러스터에서 Crashd 실행

Crashd가 실행되면 args 파일에서 인수 값을 가져와 스크립트 파일(diagnostics.crsh)에 전달합니다. 스크립트는 명령을 실행하여 Photon OS 워크로드 클러스터의 문제를 진단하는 데 도움이 되는 정보를 추출합니다.

사전 요구 사항

Crashd 스크립트 diagnostics.crsh를 실행하기 전에 로컬 시스템의 실행 경로에 다음과 같은 프로그램이 있어야 합니다.

  • kubectl
  • scp
  • ssh

    참고

    부트스트랩 클러스터의 문제를 조사할 때 로컬에 kind(v0.7.0 이상) 명령이 설치되어 있어야 합니다.

또한 Crashd를 실행하기 전에 다음 단계를 수행해야 합니다.

  • SSH 개인/공용 키 쌍을 사용하여 Crashd를 구성합니다.
  • Tanzu Kubernetes Grid VM이 SSH 공용 키를 사용하도록 구성되어 있는지 확인합니다.
  • kubeconfig 명령을 사용하여 관리 클러스터의 tanzu mc kubeconfig get <management-cluster-name> 파일을 추출합니다.
  • 간단하게 설치하려면 kubeconfig, public-key 파일, diagnostics.crsh 파일, args 파일이 동일한 위치에 있는지 확인합니다.
  • 문제 해결 중인 워크로드 클러스터를 배포하기 위해 생성된 클러스터 이외의 로컬 kind 클러스터를 삭제합니다.

    • docker ps를 실행하여 현재 실행 중인 kind 클러스터를 식별합니다.
    • kind를 실행하여 다른 kind delete cluster --name CLUSTER-NAME 클러스터를 삭제합니다.

Crashd 구성

  1. Crashd 번들을 다운로드하고 압축을 푼 위치로 이동합니다.

  2. 텍스트 편집기에서 기존 인수 파일 args를 아래 코드로 덮어씁니다. 이 파일에는 CrashD 스크립트에 전달할 키/값 쌍이 포함되어 있습니다.

    # ######################################################
    # Crashd script argument file
    #
    # This file defines CLI argument values that are passed
    # Crashd when running scripts for troubleshooting TKG
    # clusters.
    # ######################################################
    
    # target: specifies cluster to target.
    # Valid targets are: {bootstrap, mgmt, workload}
    target=mgmt
    
    # infra: the underlying infrastructure used by the TKG cluster.
    # Valid values are: {vsphere, aws, azure}
    infra=vsphere
    
    # workdir: a local directory where collected files are staged.
    workdir=./workdir
    
    # ssh_user: the user ID used for SSH connections to cluster nodes.
    ssh_user=capv
    
    # ssh_pk_file: the path to the private key file created to SSH
    # into cluster nodes.
    ssh_pk_file=./capv.pem
    
    # ######################################################
    # Management Cluster
    # The following arguments are used to collect information
    # from a management cluster or named workload clusters.
    # ######################################################
    
    # mgmt_cluster_config: the kubeconfig file path for the management cluster.
    mgmt_cluster_config=./tkg_cluster_config
    
    # ######################################################
    # Workload Cluster
    # The following arguments are used to collect information
    # from one or more workload clusters that are managed
    # by the management cluster configured above.
    # ######################################################
    
    # workload_clusters: a comma separated list of workload cluster names
    # [uncomment below]
    #workload_clusters=tkg-cluster-wc-498
    
    # workload_cluster_ns: the namespace where the workload cluster
    # is hosted in the management plane.
    # Note: it's actually the namespace in which the secrets/${workload_cluster_name}-kubeconfig
    # is created in the management cluster.
    # [uncomment below]
    #workload_cluster_ns=default
    
  3. SSH 개인 키 파일의 로컬 경로를 기록합니다. SSH 키 쌍이 아직 없거나 새 키 쌍을 생성하려면 SSH 키 쌍 생성에 설명된 대로 ssh-keygen을 실행합니다. 예:

    ssh-keygen -t rsa -b 4096 -C "[email protected]"

    메시지가 표시되면 파일 위치의 로컬 경로를 입력합니다. SSH 키 쌍 생성에 대한 자세한 내용.

  4. args 파일에 다음 인수를 설정합니다.

    • target: 이 값을 다음으로 설정합니다.
      • 로컬 부트스트랩 독립형 관리 클러스터를 진단하려면 bootstrap
      • 배포된 독립형 관리 클러스터를 진단하려면 mgmt
      • 하나 이상의 워크로드 클러스터를 진단하려면 workload
    • infra: 클러스터의 기본 인프라: aws, azure 또는 vsphere.
    • workdir: 파일이 수집되는 위치입니다.
    • ssh_user: 클러스터 시스템에 액세스하는 데 사용되는 SSH 사용자입니다. vSphere에서 실행 중인 클러스터의 경우 사용자 이름은 capv입니다.
    • ssh_pk_file: SSH 개인 키 파일의 경로입니다.
    • mgmt_cluster_config 관리 클러스터의 kubeconfig 파일 경로입니다.
  5. 워크로드 클러스터를 진단하려면 위에 있는 인수 외에 주석 처리를 제거하고 다음을 설정합니다.

    • workload_clusters: 진단 정보를 수집할 워크로드 클러스터 이름의 쉼표로 구분된 목록입니다.
    • workload_cluster_ns: secrets/WORKLOAD-CLUSTER-NAME-kubeconfig가 관리 클러스터에서 생성되는 네임스페이스입니다.

Crashd 실행

  1. 아래의 진단 파일 diagnostics.crsh에 있는 코드가 포함된 충돌 스크립트 파일 diagnostics.crsh를 생성합니다.

  2. 스크립트 파일 diagnostics.crsh와 인수 파일 args가 있는 위치에서 crashd 명령을 실행합니다.

    crashd run --args-file args diagnostics.crsh
    
  3. 필요한 경우 Crashd 출력을 모니터링합니다. 기본적으로 crashd 명령은 완료될 때까지 자동으로 실행됩니다. 따라서 --debug 플래그를 사용하여 화면에 다음과 유사한 로그 메시지를 볼 수 있습니다.

    crashd run --debug --args-file args diagnostics.crsh
    
    DEBU[0003] creating working directory ./workdir/tkg-kind-12345
    DEBU[0003] kube_capture(what=objects)
    DEBU[0003] Searching in 20 groups
    ...
    DEBU[0015] Archiving [./workdir/tkg-kind-12345] in bootstrap.tkg-kind-12345.diagnostics.tar.gz
    DEBU[0015] Archived workdir/tkg-kind-12345/kind-logs/docker-info.txt
    DEBU[0015] Archived workdir/tkg-kind-12345/kind-logs/tkg-kind-12345-control-plane/alternatives.log
    DEBU[0015] Archived workdir/tkg-kind-12345/kind-logs/tkg-kind-12345-control-plane/containerd.log
    

진단 파일 diagnostics.crsh

CrashD 번들 다운로드에서 crashd run 명령에 전달할 스크립트로 기존 diagnostics.crsh 파일을 다음 코드로 덮어씁니다.

def capture_node_diagnostics(nodes):
    capture(cmd="sudo df -i", resources=nodes)
    capture(cmd="sudo crictl info", resources=nodes)
    capture(cmd="df -h /var/lib/containerd", resources=nodes)
    capture(cmd="sudo systemctl status kubelet", resources=nodes)
    capture(cmd="sudo systemctl status containerd", resources=nodes)
    capture(cmd="sudo journalctl -xeu kubelet", resources=nodes)
    capture(cmd="sudo journalctl -xeu containerd", resources=nodes)
    capture(cmd="sudo cat /var/log/cloud-init-output.log", resources=nodes)
    capture(cmd="sudo cat /var/log/cloud-init.log", resources=nodes)
def capture_windows_node_diagnostics(nodes):
    capture(cmd="Get-CimInstance -ClassName Win32_LogicalDisk", file_name="disk_info.out", resources=nodes)
    capture(cmd="(Get-ItemProperty -Path c:\\windows\\system32\\hal.dll).VersionInfo.FileVersion",file_name="windows_version_info.out", resources=nodes)
    capture(cmd="cat C:\\k\\StartKubelet.ps1 ; cat C:\\var\\lib\\kubelet\\kubeadm-flags.env", resources=nodes)
    capture(cmd="Get-Service Kubelet | select * ", resources=nodes)
    capture(cmd="Get-Service Containerd | select * ", resources=nodes)
    capture(cmd="Get-Service ovs* | select * ", resources=nodes)
    capture(cmd="Get-Service antrea-agent | select * ", resources=nodes)
    capture(cmd="Get-Service kube-proxy | select * ", resources=nodes)
    capture(cmd="Get-Service Kubelet | select * ", resources=nodes)
    capture(cmd="Get-HNSNetwork", resources=nodes)
    capture(cmd="& 'c:\\Program Files\\containerd\\crictl.exe' -r 'npipe:////./pipe/containerd-containerd' info", resources=nodes)
    capture(cmd="Get-MpPreference | select ExclusionProcess", resources=nodes)
    capture(cmd="cat c:\\var\\log\\kubelet\\kubelet.exe.INFO", resources=nodes)
    capture(cmd="cat c:\\var\\log\\antrea\\antrea-agent.exe.INFO", resources=nodes)
    capture(cmd="cat c:\\var\\log\\kube-proxy\\kube-proxy.exe.INFO", resources=nodes)
    capture(cmd="cat 'c:\\Program Files\\Cloudbase Solutions\\Cloudbase-Init\\log\\cloudbase-init-unattend.log'", resources=nodes)
    capture(cmd="cat 'c:\\Program Files\\Cloudbase Solutions\\Cloudbase-Init\\log\\cloudbase-init.log'", resources=nodes)
    copy_from(path="C:\\Windows\\System32\\Winevt\\Logs\\System.evtx", resources=nodes)
    copy_from(path="C:\\Windows\\System32\\Winevt\\Logs\\Application.evtx", resources=nodes)
    copy_from(path="c:\\openvswitch\\var\\log\\openvswitch\\ovs-vswitchd.log", resources=nodes)
    copy_from(path="c:\\openvswitch\\var\\log\\openvswitch\\ovsdb-server.log", resources=nodes)
# fetches a suitable capi provider, for either capa or others (capv/capz),
# to be used for enumerating cluster machines
def fetch_provider(iaas, workload_cluster_name, ssh_cfg, kube_cfg, namespace, filter_labels):
    # workaround: vsphere and azure use same provider as they work similarly (see issue #162)
    if iaas == "vsphere" or iaas == "azure":
        provider = capv_provider(
            workload_cluster=workload_cluster_name,
            namespace=namespace,
            ssh_config=ssh_cfg,
            mgmt_kube_config=kube_cfg,
            labels=filter_labels
        )
    else:
        provider = capa_provider(
            workload_cluster=workload_cluster_name,
            namespace=namespace,
            ssh_config=ssh_cfg,
            mgmt_kube_config=kube_cfg,
            labels=filter_labels
        )
    return provider
# retrieves linux management provider for linux nodes
def fetch_mgmt_provider_linux(infra, ssh_cfg, kube_cfg, ns):
    return fetch_provider(infra, '', ssh_cfg, kube_cfg, ns, ["kubernetes.io/os=linux"])
# retrieves windows mgmt provider for windows nodes
def fetch_mgmt_provider_windows(infra, ssh_cfg, kube_cfg, ns):
    return fetch_provider(infra, '', ssh_cfg, kube_cfg, ns, ["kubernetes.io/os=windows"])
# retrieves linux workload provider for linux nodes
def fetch_workload_provider_linux(infra, wc_cluster, ssh_cfg, kube_cfg, ns):
    return fetch_provider(infra, wc_cluster, ssh_cfg, kube_cfg, ns, ["kubernetes.io/os=linux"])
# retrieves windows workload provider for windodws nodes
def fetch_workload_provider_windows(infra, wc_cluster, ssh_cfg, kube_cfg, ns):
    return fetch_provider(infra, wc_cluster, ssh_cfg, kube_cfg, ns, ["kubernetes.io/os=windows"])
def diagnose_mgmt_cluster(infra):
    # validation
    args.ssh_user
    args.ssh_pk_file
    args.mgmt_cluster_config
    if len(infra) == 0:
        print("Infra argument not provided")
        return
    wd = "{}/tkg-mgmt-cluster".format(args.workdir)
    conf = crashd_config(workdir=wd)
    ssh_conf = ssh_config(username=args.ssh_user, private_key_path=args.ssh_pk_file)
    kube_conf = kube_config(path=args.mgmt_cluster_config)
    # fetch linux mgmt node diagnostics
    mgmt_provider_linux = fetch_mgmt_provider_linux(infra, ssh_conf, kube_conf, '')
    lin_nodes = resources(provider=mgmt_provider_linux)
    capture_node_diagnostics(lin_nodes)
    # fetch win mgmt node diagnostics
    mgmt_provider_win = fetch_mgmt_provider_windows(infra, ssh_conf, kube_conf, '')
    win_nodes = resources(provider=mgmt_provider_win)
    if len(win_nodes) > 0:
        capture_windows_node_diagnostics(win_nodes)
    #add code to collect pod info from cluster
    set_defaults(kube_config(capi_provider = mgmt_provider_linux))
    pods_ns=[
        "capi-kubeadm-bootstrap-system",
        "capi-kubeadm-control-plane-system",
        "capi-system",
        "capi-webhook-system",
        "cert-manager",
        "tkg-system",
        "kube-system",
        "tkr-system",
        "capa-system",
        "capv-system",
        "capz-system",
    ]
    if infra == "vsphere":
        pods_ns.append("tkg-system-networking")
        pods_ns.append("avi-system")
    kube_capture(what="logs", namespaces=pods_ns)
    kube_capture(what="objects", kinds=["pods", "services"], namespaces=pods_ns)
    kube_capture(what="objects", kinds=["deployments", "replicasets"], groups=["apps"], namespaces=pods_ns)
    kube_capture(what="objects", kinds=["apps"], groups=["kappctrl.k14s.io"], namespaces=["tkg-system"])
    kube_capture(what="objects", kinds=["tanzukubernetesreleases"], groups=["run.tanzu.vmware.com"])
    kube_capture(what="objects", kinds=["configmaps"], namespaces=["tkr-system"])
    kube_capture(what="objects", categories=["cluster-api"])
    kube_capture(what="objects", groups=["ipam.cluster.x-k8s.io"])
    if infra == "vsphere":
        kube_capture(what="objects", kinds=["akodeploymentconfigs"])
    archive(output_file="tkg-mgmt.diagnostics.tar.gz", source_paths=[conf.workdir])
def diagnose_workload_cluster(infra, name):
    # validation
    args.infra
    args.ssh_user
    args.ssh_pk_file
    args.mgmt_cluster_config
    workload_ns = args.workload_cluster_ns
    if len(infra) == 0:
        print("Infra argument not provided")
        return
    wd = "{}/{}".format(args.workdir, name)
    conf = crashd_config(workdir=wd)
    ssh_conf = ssh_config(username=args.ssh_user, private_key_path=args.ssh_pk_file)
    kube_conf = kube_config(path=args.mgmt_cluster_config)
    # fetch linux workload node diagnostics
    wc_provider_linux = fetch_workload_provider_linux(infra, name, ssh_conf, kube_conf, workload_ns)
    lin_nodes = resources(provider=wc_provider_linux)
    capture_node_diagnostics(lin_nodes)
    # fetch win workload node diagnostics
    wc_provider_win = fetch_workload_provider_windows(infra, name, ssh_conf, kube_conf, workload_ns)
    win_nodes = resources(provider=wc_provider_win)
    if len(win_nodes) > 0:
        capture_windows_node_diagnostics(win_nodes)
    #add code to collect pod info from cluster
    set_defaults(kube_config(capi_provider = wc_provider_linux))
    pods_ns=["default", "kube-system", "tkg-system"]
    if infra == "vsphere":
        pods_ns.append("tkg-system-networking")
        pods_ns.append("avi-system")
    kube_capture(what="logs", namespaces=pods_ns)
    kube_capture(what="objects", kinds=["pods", "services"], namespaces=pods_ns)
    kube_capture(what="objects", kinds=["deployments", "replicasets"], groups=["apps"], namespaces=pods_ns)
    kube_capture(what="objects", kinds=["apps"], groups=["kappctrl.k14s.io"], namespaces=["tkg-system"])
    if infra == "vsphere":
        kube_capture(what="objects", kinds=["akodeploymentconfigs"])
    archive(output_file="{}.diagnostics.tar.gz".format(name), source_paths=[conf.workdir])
# extract diagnostic info from local kind boostrap cluster
def diagnose_bootstrap_cluster():
    p = prog_avail_local("kind")
    if p == "":
        print("Error: kind is not available")
        return
    clusters=get_tkg_bootstrap_clusters()
    if len(clusters) == 0:
        print("No tkg-kind bootstrap cluster found")
        return
    pod_ns=[
        "caip-in-cluster-system",
        "capi-kubeadm-bootstrap-system",
        "capi-kubeadm-control-plane-system",
        "capi-system",
        "capi-webhook-system",
        "capv-system",
        "capa-system",
        "capz-system",
        "cert-manager",
        "tkg-system",
        "tkg-system-networking",
        "avi-system",
    ]
    # for each tkg-kind cluster:
    #  - capture kind logs, export kubecfg, and api objects
    for kind_cluster in clusters:
        wd = "{}/{}".format(args.workdir, kind_cluster)
        run_local("kind export logs --name {} {}/kind-logs".format(kind_cluster, wd))
        kind_cfg = capture_local(
            cmd="kind get kubeconfig --name {0}".format(kind_cluster),
            workdir="./",
            file_name="{}.kubecfg".format(kind_cluster)
        )

        conf = crashd_config(workdir=wd)
        set_defaults(kube_config(path=kind_cfg))
        kube_capture(what="objects", kinds=["pods", "services"], namespaces=pod_ns)
        kube_capture(what="objects", kinds=["deployments", "replicasets"], groups=["apps"], namespaces=pod_ns)
        kube_capture(what="objects", categories=["cluster-api"])
        kube_capture(what="objects", kinds=["akodeploymentconfigs"])
        archive(output_file="bootstrap.{}.diagnostics.tar.gz".format(kind_cluster), source_paths=[conf.workdir])
# return tkg clusters in kind (tkg-kind-xxxx)
def get_tkg_bootstrap_clusters():
    clusters = run_local("kind get clusters").split('\n')
    result = []
    for cluster in clusters:
        if cluster.startswith("tkg-kind"):
            result.append(cluster)

    return result
def check_prereqs():
    # validate args
    args.workdir
    p = prog_avail_local("ssh")
    if p == "":
        print("Error: ssh is not available")
        return False

    p = prog_avail_local("scp")
    if p == "":
        print("Error: scp is not available")
        return False

    p = prog_avail_local("kubectl")
    if p == "":
        print("Error: kubectl is not available")
        return False

    return True

def diagnose(target, infra):
    # validation
    if not check_prereqs():
        print("Error: One or more prerequisites are missing")
        return
    # run diagnostics
    if target == "bootstrap":
        diagnose_bootstrap_cluster()
    elif target == "mgmt":
        diagnose_mgmt_cluster(infra)
    elif target == "workload":
        for name in args.workload_clusters.split(","):
            diagnose_workload_cluster(infra, name)
    else:
        print("Error: unknown target {}".format(target))
diagnose(args.target, args.infra)
check-circle-line exclamation-circle-line close-line
Scroll to top icon