在企业现有的 Kubernetes 基础设施、服务网格 (Istio) 和 Argo 环境中部署 Kubeflow 1.3 带来了一系列挑战。本文将探讨如何在克服这些挑战的同时,保留组织和 Kubeflow 推荐的最佳实践。

Intuit 的现状

Intuit 在构建强大的 Kubernetes 基础设施方面投入巨大,该基础设施支撑着 Intuit 的所有产品:TurboTax、QuickBooks 和 Mint。有数千个服务运行在一百多个 Kubernetes 集群上。管理这些集群的是 Intuit Kubernetes Service (IKS) 控制平面。IKS 控制平面提供命名空间管理、角色管理、隔离等服务。连接这些服务的是一个先进的、基于 Istio 的服务网格,它补充了 Intuit 的 API Gateway。两者结合提供了强大的身份验证、授权、速率限制和其他路由功能。

Intuit ML 平台建立在此生态系统之上,利用 Intuit Kubernetes 基础设施和 AWS SageMaker 的优势,提供模型训练、推理和特征管理能力。这是我们开始探索 Kubeflow 的背景,旨在提供高级编排、实验和其他服务。

Kubeflow 与 Istio

我们在运行 Kubeflow 时遇到的第一个挑战是 Kubeflow 自带的 Istio 与 Intuit 基于 Istio 构建的现有服务网格之间的兼容性。出现了两个关键问题:版本兼容性和运维维护。

Kubeflow v1.3 默认使用 Istio (v1.9),幸运的是它与 Intuit 正在运行的旧版本 Istio (v1.6) 兼容。运行两个 Istio 版本是不切实际的,因为那会丧失大型、互联的现有服务网格带来的好处。因此,我们希望 Kubeflow 能与 Intuit 运行 Istio v1.6 的服务网格无缝协作。

如果您刚接触 Istio,可能需要了解这些关键的流量管理组件安全组件的入门知识

  1. VirtualService
  2. DestinationRule
  3. Gateway
  4. EnvoyFilter
  5. AuthorizationPolicy

步骤 1:从 Kubeflow 中移除默认的 Istio 配置和 Argo

运行 Kubeflow 的第一步是移除 Kubeflow 捆绑的 Istio 和 Argo,以便将其与 Intuit 服务网格集成。

移除 Kubeflow 的默认 Istio

我们使用 Kustomize 构建了 Kubeflow 安装所需的 Manifest,并使用 ArgoCD 部署 Kubeflow Kubernetes Manifest。

.
├── base                        # Base folder for the kubeflow out of the box manifests
│   ├── kustomization.yaml      
│   ├── pipelines               # Folder for Kubeflow Pipelines module
│   │   ├── kustomization.yaml
│   ├── other modules           # Similar to the Pipelines module you can bring other modules as well
│       ├── kustomization.yaml
├── envs                        # Folder for all the Kubeflow environments
│   ├── prod               
│   │   ├── kustomization.yaml
│   ├── dev               
│       ├── kustomization.yaml

base -> kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- github.com/kubeflow/manifests/common/kubeflow-roles/base?ref=v1.3.0
- github.com/kubeflow/manifests/common/kubeflow-namespace/base?ref=v1.3.0
- github.com/kubeflow/manifests/common/oidc-authservice/base?ref=v1.3.0
- github.com/kubeflow/manifests/apps/admission-webhook/upstream/overlays/cert-manager?ref=v1.3.0
- github.com/kubeflow/manifests/apps/profiles/upstream/overlays/kubeflow?ref=v1.3.0
- github.com/kubeflow/manifests/apps/centraldashboard/upstream/overlays/istio?ref=v1.3.0
- pipelines

base -> pipelines -> kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
  - github.com/kubeflow/pipelines/manifests/kustomize/base/installs/multi-user?ref=1.5.0
  - github.com/kubeflow/pipelines/manifests/kustomize/base/metadata/base?ref=1.5.0
  - github.com/kubeflow/pipelines/manifests/kustomize/base/metadata/options/istio?ref=1.5.0
  # To remove the default Argo from Pipelines module
  # - github.com/kubeflow/pipelines/manifests/kustomize/third-party/argo/installs/cluster?ref=1.5.0
  - github.com/kubeflow/pipelines/manifests/kustomize/third-party/mysql/base?ref=1.5.0
  - github.com/kubeflow/pipelines/manifests/kustomize/third-party/mysql/options/istio?ref=1.5.0
  - github.com/kubeflow/pipelines/manifests/kustomize/third-party/minio/base?ref=1.5.0
  - github.com/kubeflow/pipelines/manifests/kustomize/third-party/minio/options/istio?ref=1.5.0
  - github.com/kubeflow/pipelines/manifests/kustomize/third-party/metacontroller/base?ref=1.5.0

# Identifier for application manager to apply ownerReference.
# The ownerReference ensures the resources get garbage collected
# when application is deleted.
commonLabels:
  application-crd-id: kubeflow-pipelines

# !!! If you want to customize the namespace,
# please also update base/cache-deployer/cluster-scoped/cache-deployer-clusterrolebinding.yaml
namespace: kubeflow

注意:我们不得不为 pipelines 创建一个单独的文件夹,因为我们不想使用 Pipelines 模块附带的 Argo。如果您可以使用默认的 Argo,那么可以直接使用 https://github.com/kubeflow/manifests/apps/pipeline/upstream/env/platform-agnostic-multi-user-pns?ref=v1.3.0 而不是 pipelines 文件夹。

如果您不想使用 ArgoCD,可以使用 kustomize build 命令构建 Manifest,这本质上是 ArgoCD 所做的事情。上述配置已在 Kustomize 3.8.x 和 4.0.x 上测试过,两者均可正常工作。

步骤 2:使用 Kustomize 定制 Kubeflow Manifest

考虑到 Intuit 的托管 Kubernetes 生态系统,服务间通信和命名空间隔离的协议是有特定要求的,我们需要进行以下更改:

  1. 通过在命名空间规范中添加标签 istio-injection: enabled,为 Kubeflow 命名空间启用Istio 注入。Istio 然后使用此标签将 Sidecar 添加到命名空间中。
  2. 通过为 Deployments 和 StatefulSets 添加注解 sidecar.istio.io/inject: "true",以及一些 Intuit 特定的自定义标签和注解,为 Kubeflow 中的所有 Deployment 和 StatefulSet 启用 Sidecar 注入。
  3. Intuit 的安全策略禁止直接使用外部容器注册表。Intuit 内部容器注册表定期运行漏洞扫描,并认证 Docker 镜像可在各种环境中使用。内部容器注册表还有一个允许列表,允许对外部注册表进行代理,并使其符合同样的高安全标准。我们已为所有 Kubeflow 容器启用此功能。
  4. 修改 VirtualService,将所有流量从一个中心 Gateway 路由,而不是使用 Kubeflow Gateway。

我们使用 Kustomize 修改了 Kubeflow 应用 Manifest。

  1. 对于添加标签,我们使用了 LabelTransformer
         apiVersion: builtin
         kind: LabelTransformer
         metadata:
           name: deployment-labels
         labels:
           <Intuit custom labels>
           istio-injected: "true"
         fieldSpecs:
         - path: spec/template/metadata/labels
           kind: Deployment
           create: true
         - path: spec/template/metadata/labels
           kind: StatefulSet
           create: true
    
  2. 对于添加注解,我们使用了 AnnotationsTransformer

     apiVersion: builtin
     kind: AnnotationsTransformer
     metadata:
       name: deployment-annotations
     annotations:
       <Intuit custom annotations>
       sidecar.istio.io/inject: "true"
     fieldSpecs:
     - path: spec/template/metadata/annotations
       kind: Deployment
       create: true
     - path: spec/template/metadata/annotations
       kind: StatefulSet
       create: true
    
  3. 对于替换 Docker 镜像 URL,我们使用了 ImageTagTransformer

     apiVersion: builtin
     kind: ImageTagTransformer
     metadata:
       name: image-transformer-1
     imageTag:
       name: gcr.io/ml-pipeline/cache-deployer
       newName: docker.intuit.com/gcr-rmt/ml-pipeline/cache-deployer
    

    对于任何需要通过代理访问互联网的组织来说,将所有容器镜像克隆到组织内部是最佳方案,因为这样就不需要互联网来访问这些镜像了。

  4. 对于转换 VirtualServices

     - op: remove
       path: /spec/hosts/0
     - op: replace
       path: /spec/gateways/0
       value: <custom gateway>
     - op: add
       path: /spec/hosts/0
       value: <kubflow host name>
     - op: add
       path: /spec/exportTo
       value: ["."]
    
  5. 整合所有配置

    envs -> prod/dev -> kustomization.yaml

     apiVersion: kustomize.config.k8s.io/v1beta1
     kind: Kustomization
    
     resources:
     - ../base
    
     transformers:
     - transformers/image-transformers.yaml
     - transformers/label-transformers.yaml
     - transformers/annotations-transformers.yaml
    
     patchesJson6902:
     # patch VirtualService with explicit host
     # add multiple targets like below for all the VirtualServices which you need
     - path: patches/virtual-service-hosts.yaml
       target:
         group: networking.istio.io
         version: v1alpha3
         kind: VirtualService
         name: centraldashboard
    
  6. 您可能会遇到 metadata_envoy 服务的问题,在我们这里遇到了以下错误:
     [debug][init] [external/envoy/source/common/init/watcher_impl.cc:27] init manager Server destroyed
     unable to bind domain socket with id=0 (see --base-id option)
     2021-01-29T23:32:26.680310Z error Epoch 0 exited with error: exit status 1
    

    经过查找,我们发现当您使用 Istio Sidecar 注入运行此 Docker 镜像时,会出现此问题。原因是,这两个容器本质上都是 envoyproxy 容器,并且两个容器的默认 base-id 都设置为 0。

    因此,为了使其工作,我们必须更改此Dockerfile中的 CMD。

     CMD ["/etc/envoy.yaml", "--base-id", "1"]
    

步骤 3:SSO 所需的自定义更改

使用 SSO 进行身份验证主要涉及两个组件:

  1. Authservice:它是一个 StatefulSet,运行 oidc-auth 服务。它运行在 istio-system 命名空间中,直接与 OIDC 服务通信进行身份验证。
  2. Authn-filter:这是一个 EnvoyFilter,它过滤到 authservice 的流量,检查 Kubeflow 身份验证头,并在请求未授权时重定向到 authservice。它检查名为 kubeflow-userid 的头的存在。

注意:Intuit SSO 支持 OIDC,因此我们不需要使用 dex 进行集成。如果您的组织 SSO 不支持 OIDC,您可以在中间使用 dex;详细信息请参考此处

对于我们的安装,我们需要启用 authservice 的服务网格功能,并且将 authservice 移动到 kubeflow 命名空间也更合理,因为该命名空间已经启用了 Istio Sidecar 注入。

authservice 上启用 Istio 网格后,默认 Manifest 还需要进行一些更改才能使其工作。authservice Pod 无法与 Intuit SSO HTTPS URL 通信,因为来自主容器 Pod 的出站流量会被 Istio Sidecar 拦截以强制执行 mtls(默认行为)。因此,我们必须排除 HTTPS 端口 (443) 以禁用 mtls。这可以通过使用注解 traffic.sidecar.istio.io/excludeOutboundPorts: "443" 来完成。

步骤 4:设置 Ingress

我们使用以下机制将 istio-ingressgateway 服务暴露为 LoadBalancer:

  1. 在 Route 53 中设置公共托管区域,添加您想使用的主机名,例如 example.com
  2. 为 Kubeflow 安装中要使用的主机名设置 ACM 证书,主机名可以是 kubeflow.example.com
  3. 通过添加一些注解来更新服务 Manifest
    # Note that the backend talks over HTTP.
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
    # TODO: Fill in with the ARN of your certificate.
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: <cert arn from step 2>
    service.beta.kubernetes.io/aws-load-balancer-security-groups: <to restrict access within org>
    # Only run SSL on the port named "https" below.
    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
    external-dns.alpha.kubernetes.io/hostname: kubeflow.example.com
    

应用新的 Manifest 后,AWS 将自动在您的托管区域 (example.com) 中添加相应的 A 和 TXT 记录,然后您就可以通过 kubeflow.example.com 访问 Kubeflow 了。

要使用 https 保护Gateway,您还可以更改 Gateway 端口并在 Gateway 中添加密钥和证书。

有关这些注解的更多信息,请参阅在 Amazon EKS 上终止 HTTPS 流量AWS 上的 SSL 支持博客。

步骤 5:使用外部 Argo 安装

Kubeflow 内部使用 Argo 工作流以工作流方式运行管道。Argo 在工作流步骤完成后生成 Artifact,如果计划使用外部 Argo,我们只需配置 Artifact Store。

  1. 在您的集群中安装 Argo 工作流,它会被安装在一个名为 argo 的命名空间中。
  2. 从 Kubeflow 中移除所有与 Argo 相关的 Manifest。
  3. 要覆盖 Artifact Store,您需要更改随Kubeflow Manifest一起提供的 ConfigMap workflow-controller-configmap。它使用 minio 作为存储,但您也可以将其配置为使用 S3。更多详细信息请参阅ArgoWorkflow Controller Configmap GitHub 页面
  4. 最新版本的 Argo 也提供了覆盖命名空间 Artifact Store 的选项。

调试技巧

  1. 检查 EnvoyFilter 是否已应用:您应该拥有 istioctl 命令行工具

    istioctl proxy-config listeners <pod name> --port 15001 -o json

    查看输出中是否列出了 Envoy Filter。有关 Istio 代理调试的更多信息,请参考此处

  2. 检查 istio-ingressgateway

     # Port forward to the first istio-ingressgateway pod
     kubectl -n istio-system port-forward $(kubectl -n istio-system get pods -listio=ingressgateway -o=jsonpath="{.items[0].metadata.name}") 15000
    
     # Get the http routes from the port-forwarded ingressgateway pod (requires jq)
     curl --silent http://localhost:15000/config_dump | jq '\''.configs.routes.dynamic_route_configs[].route_config.virtual_hosts[]| {name: .name, domains: .domains, route: .routes[].match.prefix}'\''
    
     # Get the logs of the first istio-ingressgateway pod
     # Shows what happens with incoming requests and possible errors
     kubectl -n istio-system logs $(kubectl -n istio-system get pods -listio=ingressgateway -o=jsonpath="{.items[0].metadata.name}") --tail=300
    
     # Get the logs of the first istio-pilot pod
     # Shows issues with configurations or connecting to the Envoy proxies
     kubectl -n istio-system logs $(kubectl -n istio-system get pods -listio=pilot -o=jsonpath="{.items[0].metadata.name}") discovery --tail=300
    
  3. 检查 authservice 连接性:istio-ingressgateway Pod 应该能够访问 authservice。您可以使用以下命令进行检查:

    kubectl -n istio-system exec $(kubectl -n istio-system get pods -listio=pilot -o=jsonpath="{.items[0].metadata.name}") -- curl -v http://authservice.istio-system.svc.cluster.local:8080

    此外,请确保 authservice 可以访问 dex

    在我们的案例中,authservice 位于 kubeflow 命名空间中,因此我们使用以下命令进行了相应的更改:

    kubectl -n kubeflow exec authservice-0 -- wget -q -S -O '-' <oidc auth url>/.well-known/openid-configuration

    输出应类似于:

     {
       "issuer": "http://dex.kubeflow.svc.cluster.local:5556/dex",
       "authorization_endpoint": "http://dex.kubeflow.svc.cluster.local:5556/dex/auth",
       "token_endpoint": "http://dex.kubeflow.svc.cluster.local:5556/dex/token",
       "jwks_uri": "http://dex.kubeflow.svc.cluster.local:5556/dex/keys",
       "userinfo_endpoint": "http://dex.kubeflow.svc.cluster.local:5556/dex/userinfo",
       "response_types_supported": [
         "code"
       ],
       "subject_types_supported": [
         "public"
       ],
       "id_token_signing_alg_values_supported": [
         "RS256"
       ],
       "scopes_supported": [
         "openid",
         "email",
         "groups",
         "profile",
         "offline_access"
       ],
       "token_endpoint_auth_methods_supported": [
         "client_secret_basic"
       ],
       "claims_supported": [
         "aud",
         "email",
         "email_verified",
         "exp",
         "iat",
         "iss",
         "locale",
         "name",
         "sub"
       ]
     }
    
  4. 检查服务之间的连接性:尝试使用 curlwget 从一个服务访问另一个服务。通常其中一个总是可用的,否则,您随时可以使用 apt-get 命令安装。示例用例:从 ml-pipeline 部署 Pod 中,您可以检查管道 API 是否可访问。

    kubectl -n kubeflow exec $(kubectl -n kubeflow get pods -lapp=ml-pipeline-ui -o=jsonpath="{.items[0].metadata.name}") -- wget -q -S -O '-' ml-pipeline.kubeflow.svc.cluster.local:8888/apis/v1beta1/pipelines

对 Kubeflow 社区的请求

我们在 Intuit 遇到的挑战并非独有,任何希望采用 Kubeflow 的企业都会面临这些挑战。

很高兴看到 Kubeflow 能很好地与企业现有的 Kubernetes 基础设施配合使用,而不是强制要求一套自己的基础设施。这里有一些改进生态系统的建议/错误报告,其中一些 Intuit 将与社区合作进行构建:

  1. 我们看到 Kubeflow manifest 仓库在 v1.3 中进行了主要的文件夹结构重组,但我们认为仍有改进空间。
  2. 多集群 / 多区域支持。#5467
  3. 升级总体上似乎存在问题,应该找到更好的管理方式。#5440
  4. 支持组的多租户。#4188
  5. 在任意自定义命名空间中安装 Kubeflow。#5647
  6. 现有的 Metadata 服务性能不佳,我们尝试了增加资源和水平扩展的一些设置。社区已经在开发KFP v2.0,这可能会解决很多关于 Metadata 服务的问题。

参考资料