在云原生时代,Kubernetes已经成为容器编排的事实标准。而Azure Kubernetes Service (AKS)作为微软Azure云平台提供的托管Kubernetes服务,为我们提供了便捷的方式来部署和管理容器化应用。

这篇文章将带你通过Go语言与Azure SDK一步步实现AKS集群的创建、删除、应用部署以及节点池管理,让你深入了解如何通过代码控制云端基础设施。

准备工作

在开始之前,我们需要准备以下内容:

  1. 一个Azure账号
  2. 安装Go语言环境(推荐1.18+)
  3. 在Azure上创建服务主体(Service Principal)
  4. 准备以下环境变量:
    • AZURE_SUBSCRIPTION_ID: Azure订阅ID
    • AZURE_TENANT_ID: Azure租户ID
    • AZURE_CLIENT_ID: 服务主体的客户端ID
    • AZURE_CLIENT_SECRET: 服务主体的客户端密钥
    • AZURE_RESOURCE_GROUP: 资源组名称

我们可以通过创建.env文件的方式来存储这些环境变量:

# Azure搜索服务所需的环境变量
AZURE_SUBSCRIPTION_ID=你的订阅ID
AZURE_RESOURCE_GROUP=default

# Azure认证信息
AZURE_TENANT_ID=你的租户ID
AZURE_CLIENT_ID=你的客户端ID
AZURE_CLIENT_SECRET=你的客户端密钥

一、创建AKS集群

首先,我们来看如何创建一个AKS集群。在create目录下,我们有create.go文件,它实现了创建AKS集群的功能。

环境初始化

我们首先需要加载环境变量并进行初始化:

var (
    subscriptionID string
    resourceGroup  string
)

func init() {
    // 加载.env文件,这个文件包含了Azure的认证信息
    if err := godotenv.Load(); err != nil {
        log.Fatal("Error loading .env file")
    }
    // 从环境变量中获取Azure订阅ID和资源组名称
    subscriptionID = os.Getenv("AZURE_SUBSCRIPTION_ID")
    resourceGroup = os.Getenv("AZURE_RESOURCE_GROUP")
}

创建Azure认证凭据

接下来,我们需要创建Azure认证凭据,这是与Azure API交互的基础:

// 创建Azure认证凭据
cred, err := azidentity.NewClientSecretCredential(
    os.Getenv("AZURE_TENANT_ID"),     // Azure租户ID
    os.Getenv("AZURE_CLIENT_ID"),     // Azure客户端ID
    os.Getenv("AZURE_CLIENT_SECRET"), // Azure客户端密钥
    nil,
)
if err != nil {
    log.Fatal(err)
}

创建AKS集群

有了认证凭据后,我们就可以创建AKS集群了:

// 创建Azure Kubernetes Service (AKS) 客户端
client, err := armcontainerservice.NewManagedClustersClient(subscriptionID, cred, nil)
if err != nil {
    log.Fatal(err)
}

// 设置要创建的AKS集群的基本信息
clusterName := "learn-ask-tmp1" // 集群名称
location := "eastus"            // 集群部署位置

// 配置AKS集群的参数
parameters := armcontainerservice.ManagedCluster{
    Location: &location,
    Properties: &armcontainerservice.ManagedClusterProperties{
        DNSPrefix: &clusterName, // DNS前缀,用于访问集群
        // 配置节点池信息
        AgentPoolProfiles: []*armcontainerservice.ManagedClusterAgentPoolProfile{
            {
                Name:   toPtr("nodepool1"),                             // 节点池名称
                Count:  toPtr[int32](1),                                // 节点数量
                VMSize: toPtr("Standard_DS2_v2"),                       // 虚拟机规格
                Mode:   toPtr(armcontainerservice.AgentPoolModeSystem), // 节点池模式:系统节点池
            },
        },
        // 配置服务主体信息,用于集群访问Azure资源
        ServicePrincipalProfile: &armcontainerservice.ManagedClusterServicePrincipalProfile{
            ClientID: toPtr(os.Getenv("AZURE_CLIENT_ID")),
            Secret:   toPtr(os.Getenv("AZURE_CLIENT_SECRET")),
        },
    },
}

// 开始创建或更新AKS集群
poller, err := client.BeginCreateOrUpdate(context.Background(), resourceGroup, clusterName, parameters, nil)
if err != nil {
    log.Fatal(err)
}

// 等待集群创建完成
fmt.Printf("开始创建集群 %s...\n", clusterName)
_, err = poller.PollUntilDone(context.Background(), nil)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("集群创建成功!\n")

在上面的代码中,我们:

  1. 创建了AKS客户端
  2. 设置了集群名称和位置
  3. 配置了集群参数,包括DNS前缀、节点池信息(名称、数量、虚拟机规格和模式)以及服务主体信息
  4. 调用BeginCreateOrUpdate方法开始创建集群
  5. 使用PollUntilDone方法等待集群创建完成

注意点:

  • toPtr是一个辅助函数,用于创建指针类型的值,因为Azure SDK需要指针类型的参数
  • 我们创建了一个系统节点池,它会运行系统组件
  • 集群创建是一个异步操作,需要轮询等待完成

列出AKS集群

创建完集群后,我们可以列出所有AKS集群:

// 列出所有已创建的AKS集群
fmt.Println("\n列出所有集群:")
pager := client.NewListPager(nil)
for pager.More() {
    nextResult, err := pager.NextPage(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    // 打印每个集群的详细信息
    for _, v := range nextResult.Value {
        fmt.Printf("集群名称: %s\n", *v.Name)
        fmt.Printf("位置: %s\n", *v.Location)
        fmt.Printf("状态: %s\n", *v.Properties.ProvisioningState)
        fmt.Println("---")
    }
}

这里使用了分页器模式,逐页获取并打印集群信息。

二、删除AKS集群

创建完集群后,我们可能需要在不再使用时删除它,以节省资源。delete目录下的delete.go文件实现了这一功能。

环境初始化和认证

与创建集群类似,我们需要加载环境变量并创建认证凭据:

var (
    subscriptionID string // Azure 订阅 ID
    resourceGroup  string // Azure 资源组名称
)

func init() {
    // 加载 .env 文件中的环境变量
    if err := godotenv.Load(); err != nil {
        log.Fatal("Error loading .env file")
    }
    // 从环境变量中获取 Azure 订阅 ID 和资源组名称
    subscriptionID = os.Getenv("AZURE_SUBSCRIPTION_ID")
    resourceGroup = os.Getenv("AZURE_RESOURCE_GROUP")
}

// 创建 Azure 认证凭据
cred, err := azidentity.NewClientSecretCredential(
    os.Getenv("AZURE_TENANT_ID"),     // Azure AD 租户 ID
    os.Getenv("AZURE_CLIENT_ID"),     // 应用程序(服务主体)的客户端 ID
    os.Getenv("AZURE_CLIENT_SECRET"), // 应用程序的客户端密钥
    nil,
)
if err != nil {
    log.Fatal(err)
}

删除AKS集群

创建AKS客户端后,我们可以删除指定的集群:

// 创建 AKS 管理集群的客户端
client, err := armcontainerservice.NewManagedClustersClient(subscriptionID, cred, nil)
if err != nil {
    log.Fatal(err)
}

// 定义要删除的 AKS 集群名称
clusterName := "learn-ask-tmp1"
fmt.Printf("开始删除集群 %s...\n", clusterName)

// 开始删除集群操作
deletePoller, err := client.BeginDelete(context.Background(), resourceGroup, clusterName, nil)
if err != nil {
    log.Fatal(err)
}

// 等待删除操作完成
_, err = deletePoller.PollUntilDone(context.Background(), nil)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("集群删除成功!\n")

删除集群也是一个异步操作,需要轮询等待完成。我们使用BeginDelete方法开始删除操作,然后使用PollUntilDone方法等待操作完成。

三、部署应用到AKS集群

创建好集群后,我们需要部署应用到集群中。deploy目录下的main.go文件实现了这一功能。

获取Kubernetes客户端

首先,我们需要获取Kubernetes客户端,用于与AKS集群交互:

func getK8sClient() (*kubernetes.Clientset, error) {
    // 创建Azure认证凭据
    cred, err := azidentity.NewClientSecretCredential(
        os.Getenv("AZURE_TENANT_ID"),
        os.Getenv("AZURE_CLIENT_ID"),
        os.Getenv("AZURE_CLIENT_SECRET"),
        nil,
    )
    if err != nil {
        return nil, err
    }

    // 创建AKS客户端
    aksClient, err := armcontainerservice.NewManagedClustersClient(subscriptionID, cred, nil)
    if err != nil {
        return nil, err
    }

    // 获取集群凭据
    clusterCredential, err := aksClient.ListClusterUserCredentials(context.Background(), resourceGroup, clusterName, nil)
    if err != nil {
        return nil, err
    }

    // 创建临时kubeconfig文件
    kubeconfig, err := os.CreateTemp("", "kubeconfig")
    if err != nil {
        return nil, err
    }
    defer os.Remove(kubeconfig.Name())

    // 写入kubeconfig内容
    if _, err := kubeconfig.Write(clusterCredential.Kubeconfigs[0].Value); err != nil {
        return nil, err
    }

    // 从kubeconfig创建REST配置
    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig.Name())
    if err != nil {
        return nil, err
    }

    // 创建Kubernetes客户端
    return kubernetes.NewForConfig(config)
}

这个函数的流程是:

  1. 创建Azure认证凭据
  2. 创建AKS客户端
  3. 获取集群的kubeconfig
  4. 将kubeconfig写入临时文件
  5. 使用kubeconfig创建Kubernetes客户端

创建Deployment

有了Kubernetes客户端后,我们可以创建Deployment:

func createDeployment(clientset *kubernetes.Clientset, name string, replicas int32) error {
    // 创建Deployment对象
    deployment := &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name: name,
        },
        Spec: appsv1.DeploymentSpec{
            Replicas: &replicas,
            // 定义Pod选择器
            Selector: &metav1.LabelSelector{
                MatchLabels: map[string]string{
                    "app": name,
                },
            },
            // 定义Pod模板
            Template: corev1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: map[string]string{
                        "app": name,
                    },
                },
                Spec: corev1.PodSpec{
                    Containers: []corev1.Container{
                        {
                            Name:  name,
                            Image: "nginx:latest", // 这里使用的是nginx,创建后可以直接访问一个基础的web页面
                            Ports: []corev1.ContainerPort{
                                {
                                    ContainerPort: 80,
                                },
                            },
                        },
                    },
                },
            },
        },
    }

    // 在指定命名空间中创建Deployment
    _, err := clientset.AppsV1().Deployments(namespace).Create(context.Background(), deployment, metav1.CreateOptions{})
    return err
}

这个函数创建了一个带有nginx容器的Deployment,包括:

  1. 设置Deployment名称
  2. 设置副本数量
  3. 配置Pod选择器和标签
  4. 配置容器信息(使用nginx镜像,暴露80端口)

创建Service

除了Deployment,我们还需要创建Service来暴露应用:

func createService(clientset *kubernetes.Clientset, name string) error {
    // 创建Service对象
    service := &corev1.Service{
        ObjectMeta: metav1.ObjectMeta{
            Name: name,
        },
        Spec: corev1.ServiceSpec{
            // 选择器,用于匹配Pod
            Selector: map[string]string{
                "app": name,
            },
            // 定义服务端口
            Ports: []corev1.ServicePort{
                {
                    Port:       80,
                    TargetPort: intstr.FromInt(80),
                },
            },
            // 使用LoadBalancer类型,创建外部负载均衡器
            Type: corev1.ServiceTypeLoadBalancer,
        },
    }

    // 在指定命名空间中创建Service
    _, err := clientset.CoreV1().Services(namespace).Create(context.Background(), service, metav1.CreateOptions{})
    return err
}

这个函数创建了一个LoadBalancer类型的Service,包括:

  1. 设置Service名称
  2. 配置选择器(与Deployment中的Pod标签匹配)
  3. 配置服务端口(暴露80端口)
  4. 设置服务类型为LoadBalancer,这样Azure会自动创建负载均衡器

删除应用

当不再需要应用时,我们可以删除它:

func deleteDeployment(clientset *kubernetes.Clientset, name string) error {
    return clientset.AppsV1().Deployments(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
}

func deleteService(clientset *kubernetes.Clientset, name string) error {
    return clientset.CoreV1().Services(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
}

列出应用

我们还可以列出当前部署的所有应用:

func listDeployments(clientset *kubernetes.Clientset) error {
    // 获取指定命名空间中的所有Deployment
    deployments, err := clientset.AppsV1().Deployments(namespace).List(context.Background(), metav1.ListOptions{})
    if err != nil {
        return err
    }

    // 打印Deployment信息
    fmt.Println("\n列出所有Deployment:")
    for _, deployment := range deployments.Items {
        fmt.Printf("名称: %s\n", deployment.Name)
        fmt.Printf("副本数: %d\n", *deployment.Spec.Replicas)
        fmt.Printf("镜像: %s\n", deployment.Spec.Template.Spec.Containers[0].Image)
        fmt.Println("---")
    }
    return nil
}

主函数

主函数通过命令行参数来控制要执行的操作:

func main() {
    // 定义命令行参数
    action := flag.String("action", "list", "操作类型:create/delete/list")
    name := flag.String("name", "demo-app", "应用名称")
    replicas := flag.Int("replicas", 2, "副本数量")
    flag.Parse()

    // 获取Kubernetes客户端
    clientset, err := getK8sClient()
    if err != nil {
        log.Fatal(err)
    }

    // 根据action参数执行相应操作
    switch *action {
    case "create":
        fmt.Printf("开始创建应用 %s...\n", *name)
        if err := createDeployment(clientset, *name, int32(*replicas)); err != nil {
            log.Fatal(err)
        }
        if err := createService(clientset, *name); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("应用创建成功!\n")
    case "delete":
        fmt.Printf("开始删除应用 %s...\n", *name)
        if err := deleteDeployment(clientset, *name); err != nil {
            log.Fatal(err)
        }
        if err := deleteService(clientset, *name); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("应用删除成功!\n")
    case "list":
        if err := listDeployments(clientset); err != nil {
            log.Fatal(err)
        }
    default:
        fmt.Println("无效的操作类型。请使用 -action 参数指定操作类型:create/delete/list")
        flag.PrintDefaults()
    }
}

使用方法:

  • 创建应用:go run main.go -action create -name my-app -replicas 2
  • 删除应用:go run main.go -action delete -name my-app
  • 列出应用:go run main.go -action list

四、管理AKS节点池

AKS集群初始只有一个节点池,但我们可以根据需要添加更多节点池以满足不同类型工作负载的需求。nodepool目录下的main.go文件实现了节点池管理功能。

创建节点池

我们可以创建新的节点池:

func createNodePool(client *armcontainerservice.AgentPoolsClient, nodepoolName string) error {
    // 设置节点池的详细配置参数
    parameters := armcontainerservice.AgentPool{
        Properties: &armcontainerservice.ManagedClusterAgentPoolProfileProperties{
            Count:               toPtr[int32](2),                                                 // 节点数量:设置为2个节点
            VMSize:              toPtr("Standard_DS2_v2"),                                        // 虚拟机规格:2核8G内存
            Mode:                toPtr(armcontainerservice.AgentPoolModeUser),                    // 节点池模式:用户节点池(用于运行用户工作负载)
            OrchestratorVersion: toPtr("1.30.10"),                                                // Kubernetes版本:1.30.10
            Type:                toPtr(armcontainerservice.AgentPoolTypeVirtualMachineScaleSets), // 节点池类型:使用虚拟机规模集
        },
    }

    // 开始创建节点池的异步操作
    fmt.Printf("开始创建节点池 %s...\n", nodepoolName)
    poller, err := client.BeginCreateOrUpdate(context.Background(), resourceGroup, clusterName, nodepoolName, parameters, nil)
    if err != nil {
        return err
    }

    // 等待节点池创建完成
    _, err = poller.PollUntilDone(context.Background(), nil)
    if err != nil {
        return err
    }
    fmt.Printf("节点池创建成功!\n")
    return nil
}

这个函数的流程是:

  1. 设置节点池参数,包括节点数量、虚拟机规格、模式、Kubernetes版本和类型
  2. 调用BeginCreateOrUpdate方法开始创建节点池
  3. 使用PollUntilDone方法等待创建完成

注意点:

  • 节点池模式设置为AgentPoolModeUser,表示这是一个用户节点池,专门用于运行用户工作负载
  • 虚拟机规格为Standard_DS2_v2,具有2个vCPU和8GB内存
  • 节点池类型为VirtualMachineScaleSets,这是Azure推荐的可扩展节点池类型

删除节点池

当不再需要节点池时,我们可以删除它:

func deleteNodePool(client *armcontainerservice.AgentPoolsClient, nodepoolName string) error {
    // 开始删除节点池的异步操作
    fmt.Printf("开始删除节点池 %s...\n", nodepoolName)
    poller, err := client.BeginDelete(context.Background(), resourceGroup, clusterName, nodepoolName, nil)
    if err != nil {
        return err
    }

    // 等待节点池删除完成
    _, err = poller.PollUntilDone(context.Background(), nil)
    if err != nil {
        return err
    }
    fmt.Printf("节点池删除成功!\n")
    return nil
}

列出节点池

我们可以列出当前集群中的所有节点池:

func listNodePools(client *armcontainerservice.AgentPoolsClient) error {
    fmt.Println("\n列出所有节点池:")
    // 创建分页器以获取所有节点池
    pager := client.NewListPager(resourceGroup, clusterName, nil)
    for pager.More() {
        // 获取下一页的结果
        nextResult, err := pager.NextPage(context.Background())
        if err != nil {
            return err
        }
        // 遍历并打印每个节点池的详细信息
        for _, v := range nextResult.Value {
            fmt.Printf("节点池名称: %s\n", *v.Name)
            fmt.Printf("节点数量: %d\n", *v.Properties.Count)
            fmt.Printf("虚拟机规格: %s\n", *v.Properties.VMSize)
            fmt.Printf("模式: %s\n", *v.Properties.Mode)
            fmt.Println("---")
        }
    }
    return nil
}

主函数

主函数通过命令行参数来控制要执行的操作:

func main() {
    // 定义命令行参数
    action := flag.String("action", "list", "操作类型:create/delete/list") // 默认操作为list
    nodepoolName := flag.String("name", "userpool1", "节点池名称")          // 默认节点池名称为userpool1
    flag.Parse()

    // 创建Azure认证凭据,使用客户端密钥认证方式
    cred, err := azidentity.NewClientSecretCredential(
        os.Getenv("AZURE_TENANT_ID"),     // Azure租户ID
        os.Getenv("AZURE_CLIENT_ID"),     // Azure客户端ID
        os.Getenv("AZURE_CLIENT_SECRET"), // Azure客户端密钥
        nil,
    )
    if err != nil {
        log.Fatal(err)
    }

    // 创建AKS节点池客户端
    client, err := armcontainerservice.NewAgentPoolsClient(subscriptionID, cred, nil)
    if err != nil {
        log.Fatal(err)
    }

    // 根据命令行参数执行相应的操作
    switch *action {
    case "create":
        if err := createNodePool(client, *nodepoolName); err != nil {
            log.Fatal(err)
        }
    case "delete":
        if err := deleteNodePool(client, *nodepoolName); err != nil {
            log.Fatal(err)
        }
    case "list":
        if err := listNodePools(client); err != nil {
            log.Fatal(err)
        }
    default:
        fmt.Println("无效的操作类型。请使用 -action 参数指定操作类型:create/delete/list")
        flag.PrintDefaults()
    }
}

使用方法:

  • 创建节点池:go run main.go -action create -name user-pool-1
  • 删除节点池:go run main.go -action delete -name user-pool-1
  • 列出所有节点池:go run main.go -action list

总结

通过这篇文章,我们学习了如何使用Go语言和Azure SDK来管理AKS集群的完整生命周期:

  1. 创建AKS集群:使用create.go创建AKS集群,配置集群名称、位置、节点池等参数。
  2. 删除AKS集群:使用delete.go删除不再需要的AKS集群,释放资源。
  3. 部署应用:使用deploy/main.go将应用部署到AKS集群,包括创建Deployment和Service。
  4. 管理节点池:使用nodepool/main.go管理AKS集群的节点池,包括创建、删除和列出节点池。

这些代码示例可以作为你构建自己的AKS管理工具的起点,你可以根据自己的需求进行定制和扩展。通过编程方式管理AKS集群,你可以实现自动化运维,提高工作效率。

希望这篇文章对你学习和使用Azure Kubernetes Service有所帮助!如有任何问题,欢迎讨论交流。

参考资料