跳至内容

添加功能

这是我们添加功能的概述

添加 Cilium 使用 ENI IPAM 模式选项。

向 API 添加字段

我们希望将此作为一个选项,因此需要向 CiliumNetworkingSpec 添加一个字段

    // Ipam specifies the IP address allocation mode to use.
    // Possible values are "crd" and "eni".
    // "eni" will use AWS native networking for pods. Eni requires masquerade to be set to false.
    // "crd" will use CRDs for controlling IP address management.
    // Empty value will use host-scope address management.
    Ipam string `json:"ipam,omitempty"`

这里需要注意几件事

  • 我们可能可以使用布尔值来满足当前需求,但我们希望保留一些灵活性,因此使用字符串。

  • 我们为 Cilium 的当前默认模式定义了一个值 crd,因此我们将默认值 "" 视为“默认模式,无论将来是什么”。

因此,我们只需要检查 Ipam 是否为 eni 来确定配置哪种模式。

我们需要更新版本化的 API 和非版本化的 API,并根据 更新 API 文档 重新生成代码。

验证

我们应该添加一些验证来确保输入的值有效。我们目前只接受 enicrd 或空字符串。

验证在 validation.go 中完成,非常简单 - 如果某个值无效,我们只需将错误添加到一个切片中

    if v.Ipam != "" {
        // "azure" not supported by kOps
        allErrs = append(allErrs, IsValidValue(fldPath.Child("ipam"), &v.Ipam, []string{"crd", "eni"})...)

        if v.Ipam == kops.CiliumIpamEni {
            if c.CloudProvider != string(kops.CloudProviderAWS) {
                allErrs = append(allErrs, field.Forbidden(fldPath.Child("ipam"), "Cilum ENI IPAM is supported only in AWS"))
            }
            if !v.DisableMasquerade {
                allErrs = append(allErrs, field.Forbidden(fldPath.Child("disableMasquerade"), "Masquerade must be disabled when ENI IPAM is used"))
            }
        }
    }

配置 Cilium

Cilium 部署为“引导附加组件”,是一组位于 upup/models/cloudup/resources/addons/networking.cilium.io 下的资源模板文件,每个 Kubernetes 版本范围对应一个文件。这些文件由 upup/pkg/fi/cloudup/bootstrapchannelbuilder.go 引用

首先,我们添加到 cilium-config ConfigMap 中

  {{ with .Ipam }}
  ipam: {{ . }}
  {{ if eq . "eni" }}
  enable-endpoint-routes: "true"
  auto-create-cilium-node-resource: "true"
  blacklist-conflicting-routes: "false"
  {{ end }}
  {{ end }}

然后,我们将 cilium-operator 有条件地移动到主节点

      {{ if eq .Ipam "eni" }}
      nodeSelector:
        node-role.kubernetes.io/master: ""
      tolerations:
      - effect: NoSchedule
        key: node-role.kubernetes.io/master
      - effect: NoExecute
        key: node.kubernetes.io/not-ready
        operator: Exists
        tolerationSeconds: 300
      - effect: NoExecute
        key: node.kubernetes.io/unreachable
        operator: Exists
        tolerationSeconds: 300
      {{ end }}

更改清单文件后,请记住运行 bash hack/update-expected.sh 以获取更新的 manifestHash 值。

配置 kubelet

当 Cilium 处于 ENI 模式时,需要使用本地 IP 地址配置 kubelet,以便它可以将其与 ENI 使用的辅助 IP 地址区分开来。Kubelet 由 nodeup 在 nodeup/pkg/model/kubelet.go 中配置。该代码在 NodeupModelContextUsesSecondaryIP() 接收器返回 true 时将本地 IP 地址传递给 kubelet

因此,我们修改 UsesSecondaryIP() 以在 Cilium 处于 ENI 模式时也返回 true

return (c.Cluster.Spec.Networking.CNI != nil && c.Cluster.Spec.Networking.CNI.UsesSecondaryIP) || c.Cluster.Spec.Networking.AmazonVPC != nil || c.Cluster.Spec.Networking.LyftVPC != nil ||
    (c.Cluster.Spec.Networking.Cilium != nil && c.Cluster.Spec.Networking.Cilium.Ipam == kops.CiliumIpamEni)

配置 IAM

当 Cilium 处于 ENI 模式时,主节点上的 cilium-operator 需要额外的 IAM 权限。主节点的 IAM 权限由 pkg/model/iam/iam_builder.go 中的 BuildAWSPolicyMaster() 构建

    if b.Cluster.Spec.Networking != nil && b.Cluster.Spec.Networking.Cilium != nil && b.Cluster.Spec.Networking.Cilium.Ipam == kops.CiliumIpamEni {
        addCiliumEniPermissions(p, resource, b.Cluster.Spec.IAM.Legacy)
    }
func addCiliumEniPermissions(p *Policy, resource stringorslice.StringOrSlice) {
    p.Statement = append(p.Statement,
        &Statement{
            Effect: StatementEffectAllow,
            Action: stringorslice.Slice([]string{
                "ec2:DescribeSubnets",
                "ec2:AttachNetworkInterface",
                "ec2:AssignPrivateIpAddresses",
                "ec2:UnassignPrivateIpAddresses",
                "ec2:CreateNetworkInterface",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DescribeVpcPeeringConnections",
                "ec2:DescribeSecurityGroups",
                "ec2:DetachNetworkInterface",
                "ec2:DeleteNetworkInterface",
                "ec2:ModifyNetworkInterfaceAttribute",
                "ec2:DescribeVpcs",
            }),
            Resource: resource,
        },
    )
}

测试

在对真实情况进行测试之前,编写一些单元测试会很方便。

我们应该测试验证是否按预期工作(在 validation_test.go 中)

func Test_Validate_Cilium(t *testing.T) {
    grid := []struct {
        Cilium         kops.CiliumNetworkingSpec
        Spec           kops.ClusterSpec
        ExpectedErrors []string
    }{
        {
            Cilium: kops.CiliumNetworkingSpec{},
        },
        {
            Cilium: kops.CiliumNetworkingSpec{
                Ipam: "crd",
            },
        },
        {
            Cilium: kops.CiliumNetworkingSpec{
                DisableMasquerade: true,
                Ipam:              "eni",
            },
            Spec: kops.ClusterSpec{
                CloudProvider: "aws",
            },
        },
        {
            Cilium: kops.CiliumNetworkingSpec{
                DisableMasquerade: true,
                Ipam:              "eni",
            },
            Spec: kops.ClusterSpec{
                CloudProvider: "aws",
            },
        },
        {
            Cilium: kops.CiliumNetworkingSpec{
                Ipam: "foo",
            },
            ExpectedErrors: []string{"Unsupported value::cilium.ipam"},
        },
        {
            Cilium: kops.CiliumNetworkingSpec{
                Ipam: "eni",
            },
            Spec: kops.ClusterSpec{
                CloudProvider: "aws",
            },
            ExpectedErrors: []string{"Forbidden::cilium.disableMasquerade"},
        },
        {
            Cilium: kops.CiliumNetworkingSpec{
                DisableMasquerade: true,
                Ipam:              "eni",
            },
            Spec: kops.ClusterSpec{
                CloudProvider: "gce",
            },
            ExpectedErrors: []string{"Forbidden::cilium.ipam"},
        },
    }
    for _, g := range grid {
        g.Spec.Networking = &kops.NetworkingSpec{
            Cilium: &g.Cilium,
        }
        errs := validateNetworkingCilium(&g.Spec, g.Spec.Networking.Cilium, field.NewPath("cilium"))
        testErrors(t, g.Spec, errs, g.ExpectedErrors)
    }
}

文档

如果您的功能影响 configcluster.spec 中的重要配置选项,请在 cluster_spec.md 中记录它们。

测试

您可以本地 make 并运行 kops。但 nodeup 是从 S3 存储桶中提取的。

要快速测试 nodeup 的更改,您可以构建它,将其通过 scp 传输到运行的机器,并通过 SSH 运行它,并在本地查看输出

make push-aws-run-amd64 TARGET=admin@<publicip>

但是,为了进行更完整的测试,您可能需要执行 nodeup 的私有构建,并从头开始启动集群。

为此,您可以通过设置 KOPS_BASE_URL 环境变量来重新指定 nodeup 源 URL,然后使用以下命令推送 nodeup

export S3_BUCKET_NAME=<yourbucketname>
make kops-install dev-upload UPLOAD_DEST=s3://${S3_BUCKET_NAME}

KOPS_VERSION=`.build/dist/$(go env GOOS)/$(go env GOARCH)/kops version --short`
export KOPS_BASE_URL=https://${S3_BUCKET_NAME}.s3.amazonaws.com/kops/${KOPS_VERSION}/
export KOPS_ARCH=amd64
kops create cluster <clustername> --zones us-east-1b
...

如果您更改了 dns 或 kOps 控制器,您也需要测试它们。为此,请在创建集群之前运行下面的代码段。

对于 dns-controller

KOPS_VERSION=`.build/dist/$(go env GOOS)/$(go env GOARCH)/kops version -- --short`
export DOCKER_IMAGE_PREFIX=${USER}/
export DOCKER_REGISTRY=
make dns-controller-push
export DNSCONTROLLER_IMAGE=${DOCKER_IMAGE_PREFIX}dns-controller:${KOPS_VERSION}

对于 kops-controller

KOPS_VERSION=`.build/dist/$(go env GOOS)/$(go env GOARCH)/kops version -- --short`
export DOCKER_IMAGE_PREFIX=${USER}/
export DOCKER_REGISTRY=
make kops-controller-push
export KOPSCONTROLLER_IMAGE=${DOCKER_IMAGE_PREFIX}kops-controller:${KOPS_VERSION}

使用该功能

用户只需 kops edit cluster 并添加类似以下的值

  spec:
    networking:
      cilium:
        disableMasquerade: true
        ipam: eni

然后 kops update cluster --yes 将创建新的 NodeUpConfig,该配置包含在实例启动脚本中,因此需要新的 LaunchTemplate 版本,因此需要 kops-rolling update。我们正在努力改变设置,使其无需重启即可生效,但对于此特定设置,您可能并不需要经常更改它。

其他步骤

  • 我们还可以为 create cluster 创建一个 CLI 标志。在本例中,这似乎不值得;这是一个比较高级的选项。