fupanc's blog

client-go

这是k8s官方提供的与kubernetes集群通信的Go客户端,可以直接通过这个sdk来操纵k8s集群。这里来过一下这个库的一些使用方式。

安装

下载go语言

我用两个阿里云服务器搭建了一个k3s集群,简单记录一下如何在阿里云的宝塔linux服务器上安装go:

wget https://mirrors.aliyun.com/golang/go1.26.2.linux-amd64.tar.gz

然后如下操作即可:

rm -rf /usr/local/go && tar -C /usr/local -xzf go1.26.2.linux-amd64.tar.gz

添加如下命令到/etc/profile文件中:

export PATH=$PATH:/usr/local/go/bin

随后验证一下即可:

go version

image-20260506175005443

然后记得修改镜像源,防止由于代理问题导致不能正常拉取需要的库:

go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct

image-20260509164915359

————————————————

安装sdk库

直接按照官方给的安装最新版本的即可:

go get k8s.io/client-go@latest

在实际使用中还需要添加如下的库:

go get k8s.io/api@latest
go get k8s.io/apimachinery@latest

后续就基于这些sdk库来简单操作一下k8s集群。

功能场景

官方提供了一些example来进行参考:

https://github.com/kubernetes/client-go/tree/master/examples

后续基于这个来进行简单了解一下场景下的代码使用方式。

注意在单linxu环境中运行要先在服务器中创建一个go项目,在一个单独的文件夹下执行如下命令即可:

go mod init main

image-20260509165332039

这样后续代码运行需要的库就会加在这个go.mod文件中。

基本功能

展示一下如何连接k8s集群以及查看集群中的信息,使用代码如下:

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
)

func main() {
	// 创建配置(自动识别 k3s/k8s)
	config, configPath, clusterType, err := getKubeConfig()
	if err != nil {
		fmt.Printf("❌ 连接失败: %v\n", err)
		fmt.Println("\n提示:")
		fmt.Println("  - k3s 用户: sudo go run k8s_simple_example.go")
		fmt.Println("  - k8s 用户: go run k8s_simple_example.go")
		return
	}

	fmt.Printf("✓ 集群类型: %s\n", clusterType)
	fmt.Printf("✓ 配置文件: %s\n", configPath)

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	fmt.Println("✓ 连接成功\n")

	// 3. 查看所有 Pods
	fmt.Println("=== Pod 信息 ===")
	pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		panic(err.Error())
	}

	fmt.Printf("总共 %d 个 Pod:\n", len(pods.Items))
	for i, pod := range pods.Items {
		fmt.Printf("%d. [%s] %s\n", i+1, pod.Namespace, pod.Name)
		fmt.Printf("   IP: %s | 节点: %s | 状态: %s\n",
			pod.Status.PodIP, pod.Spec.NodeName, pod.Status.Phase)
	}

	// 4. 查看节点信息
	fmt.Println("\n=== 节点信息 ===")
	nodes, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		panic(err.Error())
	}

	fmt.Printf("总共 %d 个节点:\n", len(nodes.Items))
	for i, node := range nodes.Items {
		fmt.Printf("\n%d. 节点: %s\n", i+1, node.Name)

		// IP 地址
		for _, addr := range node.Status.Addresses {
			if addr.Type == "InternalIP" {
				fmt.Printf("   内网IP: %s\n", addr.Address)
			}
		}

		// 资源信息
		fmt.Printf("   CPU: %s (可用: %s)\n",
			node.Status.Capacity.Cpu().String(),
			node.Status.Allocatable.Cpu().String())
		fmt.Printf("   内存: %s (可用: %s)\n",
			node.Status.Capacity.Memory().String(),
			node.Status.Allocatable.Memory().String())

		// 系统信息
		fmt.Printf("   系统: %s\n", node.Status.NodeInfo.OSImage)
		fmt.Printf("   版本: %s\n", node.Status.NodeInfo.KubeletVersion)
	}

	// 5. 查看命名空间
	fmt.Println("\n=== 命名空间 ===")
	namespaces, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		panic(err.Error())
	}

	fmt.Printf("总共 %d 个命名空间: ", len(namespaces.Items))
	for i, ns := range namespaces.Items {
		if i > 0 {
			fmt.Print(", ")
		}
		fmt.Print(ns.Name)
	}
	fmt.Println()
}

// getKubeConfig 自动获取 Kubernetes 配置(k3s/k8s 通用)
func getKubeConfig() (*rest.Config, string, string, error) {
	// 配置文件路径(按优先级)
	configPaths := []struct {
		path string
		name string
	}{
		{os.Getenv("KUBECONFIG"), "环境变量"},
		{"/etc/rancher/k3s/k3s.yaml", "k3s"},
		{filepath.Join(homedir.HomeDir(), ".kube/config"), "k8s"},
		{"/etc/kubernetes/admin.conf", "k8s"},
	}

	for _, cp := range configPaths {
		if cp.path == "" {
			continue
		}
		if _, err := os.Stat(cp.path); err == nil {
			if config, err := clientcmd.BuildConfigFromFlags("", cp.path); err == nil {
				return config, cp.path, cp.name, nil
			}
		}
	}

	return nil, "", "", fmt.Errorf("未找到配置文件")
}

这里的代码实现了匹配环境是k3s还是k8s,尽量实现两种集群的灵活配置。

添加进代码后执行如下命令即可:

go get k8s.io/client-go@latest
go get k8s.io/apimachinery@latest
go mod tidy

执行这个命令后会自动下载项目中需要的库,就需要挨个挨个单独下载了。最后执行效果如下:

go run 1.go

image-20260509170611142

和运行kubectl获取到的结果一样。

管理pod

这里主要是为了实现我在dast中设计的功能场景:拉取外部镜像库并创建对应的pod。基于这个点,主要涉及到两个功能实现:

如果是公开镜像,就不需要单独搞个镜像库了,但是我这里的扫描模块都是特制的,故这里准备将相关代码制成镜像后push到镜像库中,然后再在k3s集群拉取并创建pod时pull下来,那么这样的话就需要配置登陆凭证,不然拉不下来。

最后调研下来,准备基于下面两种方式结合来实现CI以及推送镜像到容器镜像服务:

都是github提供的非常方便的用于实现这方面的功能,下面来看看要如何实现。

基本说明

Github Actions

Github Actions是一种持续集成和持续交付(CI/CD)平台,可用于自动执行生成、测试和部署管道。主要通过工作流来实现自动化。工作流是一个可配置的自动化过程,它将运行一个或多个作业,在仓库的.github/workflow目录中定义,一个仓库可以有多个工作流,每个工作流都可以执行一组不同的任务。

GHCR:

这是github提供的容器镜像服务,可以通过github actions构建镜像并推送到这个镜像仓库中,后续可以直接从这个镜像仓库拉取对应的镜像,形如:

docker pull ghcr.io/your-githubname/imagename

#私有的话还需要先login

下面来基于一个nginx的docker文件实现ci构建以及push,这里就直接在已有的代码库上操作了,在根目录新建dockerfile文件如下:

FROM nginx:alpine

COPY . /usr/share/nginx/html

EXPOSE 80

然后新建workflow文件:

image-20260511135214090

修改成如下具体模版文件内容后commit即可:

name: Build CTF Image

on:
  push:
    branches:
      - main

jobs:
  docker:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Login GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and Push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository_owner }}/ctf-web:latest

后续就会自动构建并推送镜像,最后可以在自己的github packages看到对应的镜像文件(位置在个人 =》 profile =〉packages):

image-20260511140000762

后续就可以自行拉取对应的镜像并构建容器:

image-20260511140129122

后续就不多说了。

自行理解一下这里的workflow文件**,有一个点值得提一下**:

在提交commit到main分支,就会自动执行一次ci操作,这里有个关键配置就是permissions,设置了packages可写,也就是可以把镜像推送到对应的镜像仓库中,如果这里没写直接指明这个点,就会报错:

buildx failed with: ERROR: failed to build: denied: installation not allowed to Write organization package

如果就是不写这个配置,还可以通过直接修改这个代码库的配置来达到同样的目的:

image-20260511140231005

点击settings =》 actions => general :

拉到最下面修改配置即可:

image-20260511140410727

将github_token的默认权限改成了任意可读可写,后续也就可以直接push镜像到packages上了。但实际不用改,像之前那样在模版文件中加个权限设置即可。

——————

最后再提一个点,在自动运行工作流时,是可以在github actions上看到对应的日志的: image-20260511140719664

基于这个页面来查看构建情况,未执行成功github还会给你发送邮件通知,还是很贴心的。

并且这样配置的镜像是不需要权限验证的,直接拉就行,并且在k3s集群中也可以直接针对ghcr.io拉镜像:

image-20260511164332936

可以正常访问到对应镜像的nginx页面:

image-20260511164401337

并且拉取的镜像也会保存在本地:

image-20260511170624344

后续直接用就行。

这里也就不做pull权限验证了,在具体私人自行落地时修改对应的packages权限即可:

image-20260511164104622

补充

这里补充一个知识点,k8s的默认镜像拉取策略为:

image-20260515162331814

这里的Always则代表:

image-20260515162418015

每次拉取镜像时都会优先查询镜像仓库的digest,只有相同的才使用本地缓存的进行概念股,否则都是使用新的镜像。这样很方便用于我的dast的pod镜像管理,我正准备将所有扫描模块的镜像都设置成latest,这样新拉的pod,就正好是新的pod。简单测试如下:

原来的dockerfile文件改成:

FROM nginx:latest

RUN echo 'wow,wow,wow,it is a test for using' > /usr/share/nginx/html/index.html

EXPOSE 80

等待GitHub actions拉起:

image-20260515163542077

再重新在集群里面新建容器即可:

kubectl create deployment ctf-web --image=ghcr.io/fupanc-w1n/ctf-web:latest
kubectl get pods
kubectl exec -it ctf-web-78c68b7f65-f4r2r  -- sh

image-20260515163838075

成功实现远端镜像变化集群拉取的镜像也会变化,这对于后续dast开发的自动化更新为新pod容器非常重要。

————————————————

示例代码

这里主要实现的功能有如下:

代码如下:

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
)

func main() {
	// 连接集群
	config, configPath, clusterType, err := getKubeConfig()
	if err != nil {
		fmt.Printf("❌ 连接失败: %v\n", err)
		return
	}

	fmt.Printf("✓ 集群类型: %s\n", clusterType)
	fmt.Printf("✓ 配置文件: %s\n", configPath)

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	fmt.Println("✓ 连接成功\n")

	// ==================== 测试参数(硬编码) ====================
	namespace := "default"
	deploymentName := "test-nginx-deployment"
	imageName := "ghcr.io/fupanc-w1n/ctf-web:latest"
	replicas := int32(3)

	// ==================== 1. 创建 Deployment ====================
	fmt.Println("=== 创建 Deployment ===")
	err = createDeployment(clientset, namespace, deploymentName, imageName, replicas)
	if err != nil {
		fmt.Printf("❌ 创建失败: %v\n", err)
	} else {
		fmt.Printf("✓ 成功创建 Deployment: %s (副本数: %d, 镜像: %s)\n\n", deploymentName, replicas, imageName)
	}
	
}

// ==================== 核心函数 ====================

// createDeployment 创建 Deployment
func createDeployment(clientset *kubernetes.Clientset, namespace, name, image string, replicas int32) error {
	deploymentsClient := clientset.AppsV1().Deployments(namespace)

	deployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &replicas,
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app": name,
				},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": name,
					},
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:  "app",
							Image: image,
							Ports: []corev1.ContainerPort{
								{
									ContainerPort: 80,
								},
							},
						},
					},
				},
			},
		},
	}

	_, err := deploymentsClient.Create(context.TODO(), deployment, metav1.CreateOptions{})
	return err
}

// ==================== 配置加载函数 ====================

func getKubeConfig() (*rest.Config, string, string, error) {
	configPaths := []struct {
		path string
		name string
	}{
		{os.Getenv("KUBECONFIG"), "环境变量"},
		{"/etc/rancher/k3s/k3s.yaml", "k3s"},
		{filepath.Join(homedir.HomeDir(), ".kube/config"), "k8s"},
		{"/etc/kubernetes/admin.conf", "k8s"},
	}

	for _, cp := range configPaths {
		if cp.path == "" {
			continue
		}
		if _, err := os.Stat(cp.path); err == nil {
			if config, err := clientcmd.BuildConfigFromFlags("", cp.path); err == nil {
				return config, cp.path, cp.name, nil
			}
		}
	}

	return nil, "", "", fmt.Errorf("未找到配置文件")
}

运行效果如下:

image-20260511174356790

成功创建了三个pod。

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
)

func main() {
	// 连接集群
	config, configPath, clusterType, err := getKubeConfig()
	if err != nil {
		fmt.Printf("❌ 连接失败: %v\n", err)
		return
	}

	fmt.Printf("✓ 集群类型: %s\n", clusterType)
	fmt.Printf("✓ 配置文件: %s\n", configPath)

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}
	fmt.Println("✓ 连接成功\n")

	// ==================== 测试参数(硬编码) ====================
	namespace := "default"
	deploymentName := "test-nginx-deployment"

	// ==================== 1. 修改副本数(扩容) ====================
	fmt.Println("\n=== 修改副本数(扩容到 5 个) ===")
	newReplicas := int32(5)
	err = scaleDeployment(clientset, namespace, deploymentName, newReplicas)
	if err != nil {
		fmt.Printf("❌ 扩容失败: %v\n", err)
	} else {
		fmt.Printf("✓ 成功扩容到 %d 个副本\n\n", newReplicas)
	}
}

// scaleDeployment 修改 Deployment 副本数(扩缩容)
func scaleDeployment(clientset *kubernetes.Clientset, namespace, name string, replicas int32) error {
	deploymentsClient := clientset.AppsV1().Deployments(namespace)

	// 获取当前 Deployment
	deployment, err := deploymentsClient.Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return fmt.Errorf("获取 Deployment 失败: %v", err)
	}

	// 修改副本数
	deployment.Spec.Replicas = &replicas

	// 更新 Deployment
	_, err = deploymentsClient.Update(context.TODO(), deployment, metav1.UpdateOptions{})
	return err
}

func getKubeConfig() (*rest.Config, string, string, error) {
	configPaths := []struct {
		path string
		name string
	}{
		{os.Getenv("KUBECONFIG"), "环境变量"},
		{"/etc/rancher/k3s/k3s.yaml", "k3s"},
		{filepath.Join(homedir.HomeDir(), ".kube/config"), "k8s"},
		{"/etc/kubernetes/admin.conf", "k8s"},
	}

	for _, cp := range configPaths {
		if cp.path == "" {
			continue
		}
		if _, err := os.Stat(cp.path); err == nil {
			if config, err := clientcmd.BuildConfigFromFlags("", cp.path); err == nil {
				return config, cp.path, cp.name, nil
			}
		}
	}

	return nil, "", "", fmt.Errorf("未找到配置文件")
}

运行效果如下:

image-20260511174923356

成功扩容到5个。缩容的话也是一样的,把newReplicas设置成2即可:

image-20260511175411475

其实都一样的,重点就是可以直接在初始化指定replicas来创建多个pod,然后再指定replicas来扩缩pod,然后删除的话直接删除对应的deployment即可:

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
)

func main() {
	// 连接集群
	config, configPath, clusterType, err := getKubeConfig()
	if err != nil {
		fmt.Printf("❌ 连接失败: %v\n", err)
		return
	}

	fmt.Printf("✓ 集群类型: %s\n", clusterType)
	fmt.Printf("✓ 配置文件: %s\n", configPath)

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	fmt.Println("✓ 连接成功\n")

	// ==================== 测试参数(硬编码) ====================
	namespace := "default"
	deploymentName := "test-nginx-deployment"
	
	// ==================== 4. 删除 Deployment ====================
	fmt.Println("\n=== 删除 Deployment ===")

	// 取消注释下面这行来删除 Deployment
	err = deleteDeployment(clientset, namespace, deploymentName)
	if err != nil {
		fmt.Printf("❌ 删除失败: %v\n", err)
	} else {
		fmt.Printf("✓ 成功删除 Deployment: %s\n", deploymentName)
	}
}

// deleteDeployment 删除 Deployment
func deleteDeployment(clientset *kubernetes.Clientset, namespace, name string) error {
	deploymentsClient := clientset.AppsV1().Deployments(namespace)

	deletePolicy := metav1.DeletePropagationForeground
	err := deploymentsClient.Delete(context.TODO(), name, metav1.DeleteOptions{
		PropagationPolicy: &deletePolicy,
	})

	return err
}

func getKubeConfig() (*rest.Config, string, string, error) {
	configPaths := []struct {
		path string
		name string
	}{
		{os.Getenv("KUBECONFIG"), "环境变量"},
		{"/etc/rancher/k3s/k3s.yaml", "k3s"},
		{filepath.Join(homedir.HomeDir(), ".kube/config"), "k8s"},
		{"/etc/kubernetes/admin.conf", "k8s"},
	}

	for _, cp := range configPaths {
		if cp.path == "" {
			continue
		}
		if _, err := os.Stat(cp.path); err == nil {
			if config, err := clientcmd.BuildConfigFromFlags("", cp.path); err == nil {
				return config, cp.path, cp.name, nil
			}
		}
	}

	return nil, "", "", fmt.Errorf("未找到配置文件")
}

效果如下:

image-20260511180135794

成功通过删除deployment来删除了相关的pod。

————————————

配置分发

众所周知,pod都是基于镜像拉起的无状态容器,在一些情况下需要基于同一个镜像拉起不同工作细则的容器,目前的思路是代码逻辑+ConfigMap,通过ConfigMap往pod容器中挂载文件或设置环境变量,然后通过代码构造来动态读取pod容器中的配置信息,从而实现同一镜像不同工作细则的pod容器。pod中的代码就不多说了,这里就看看代码如何通过configmap实现文件挂载,大致需要实现的流程如下:

示例代码如下:

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
)

func main() {
	// 配置参数
	namespace := "default"
	configMapName := "scanner-json1"
	deploymentName := "nginx-json-demo1"
	localFilePath := "/tmp/k3s-demo/config.json" // 本地文件路径
	mountPath := "/app/config"                   // 容器内挂载路径

	// 创建 k8s 客户端
	config, configPath, clusterType, err := getKubeConfig()
	if err != nil {
		fmt.Printf("获取 kubeconfig 失败: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("✓ 使用配置: %s (%s)\n", configPath, clusterType)

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		fmt.Printf("创建 k8s 客户端失败: %v\n", err)
		os.Exit(1)
	}

	ctx := context.Background()

	// 步骤1: 从本地文件创建 ConfigMap
	if err := createConfigMapFromFile(ctx, clientset, namespace, configMapName, localFilePath); err != nil {
		fmt.Printf("创建 ConfigMap 失败: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("✓ ConfigMap '%s' 创建成功\n", configMapName)

	// 步骤2: 创建 Deployment,挂载 ConfigMap
	if err := createDeploymentWithConfigMap(ctx, clientset, namespace, deploymentName, configMapName, mountPath); err != nil {
		fmt.Printf("创建 Deployment 失败: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("✓ Deployment '%s' 创建成功,ConfigMap 已挂载到 %s\n", deploymentName, mountPath)
}

// getKubeConfig 获取 Kubernetes 配置(自动检测多种路径)
func getKubeConfig() (*rest.Config, string, string, error) {
	configPaths := []struct {
		path string
		name string
	}{
		{os.Getenv("KUBECONFIG"), "环境变量"},
		{"/etc/rancher/k3s/k3s.yaml", "k3s"},
		{filepath.Join(homedir.HomeDir(), ".kube/config"), "k8s"},
		{"/etc/kubernetes/admin.conf", "k8s"},
	}

	for _, cp := range configPaths {
		if cp.path == "" {
			continue
		}
		if _, err := os.Stat(cp.path); err == nil {
			if config, err := clientcmd.BuildConfigFromFlags("", cp.path); err == nil {
				return config, cp.path, cp.name, nil
			}
		}
	}

	return nil, "", "", fmt.Errorf("未找到配置文件")
}

// createConfigMapFromFile 从本地文件创建 ConfigMap
func createConfigMapFromFile(ctx context.Context, clientset *kubernetes.Clientset, namespace, name, filePath string) error {
	// 读取本地文件内容
	content, err := os.ReadFile(filePath)
	if err != nil {
		return fmt.Errorf("读取文件 %s 失败: %w", filePath, err)
	}

	// 提取文件名作为 ConfigMap 的 key
	fileName := filepath.Base(filePath)

	// 构造 ConfigMap 对象
	configMap := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
		},
		Data: map[string]string{
			fileName: string(content),
		},
	}

	// 创建 ConfigMap
	_, err = clientset.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, metav1.CreateOptions{})
	if err != nil {
		return fmt.Errorf("创建 ConfigMap 失败: %w", err)
	}

	return nil
}

// createDeploymentWithConfigMap 创建 Deployment 并挂载 ConfigMap
func createDeploymentWithConfigMap(ctx context.Context, clientset *kubernetes.Clientset, namespace, deploymentName, configMapName, mountPath string) error {
	replicas := int32(1)

	deployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deploymentName,
			Namespace: namespace,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &replicas,
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app": deploymentName,
				},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": deploymentName,
					},
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:  "nginx",
							Image: "nginx:latest",
							VolumeMounts: []corev1.VolumeMount{
								{
									Name:      "json-volume",
									MountPath: mountPath,
									ReadOnly:  true,
								},
							},
						},
					},
					Volumes: []corev1.Volume{
						{
							Name: "json-volume",
							VolumeSource: corev1.VolumeSource{
								ConfigMap: &corev1.ConfigMapVolumeSource{
									LocalObjectReference: corev1.LocalObjectReference{
										Name: configMapName,
									},
								},
							},
						},
					},
				},
			},
		},
	}

	// 创建 Deployment
	_, err := clientset.AppsV1().Deployments(namespace).Create(ctx, deployment, metav1.CreateOptions{})
	if err != nil {
		return fmt.Errorf("创建 Deployment 失败: %w", err)
	}

	return nil
}

新建文件:

mkdir -p /tmp/k3s-demo
cat > /tmp/k3s-demo/config.json <<'EOF'
{
  "target": "example.com",
  "mode": "scan",
  "threads": 10
}
EOF

image-20260515165806199

随后运行go文件即可:

image-20260515170317900

随后可以查看相关内容:

kubectl describe configmap scanner-json1

image-20260515170417972

成功在configmap中存入键值对,再进入pod容器查看是否成功挂载:

kubectl exec -it nginx-json-demo1-f6f7bf48c-vk9w2 -- sh

image-20260515170648718

成功通过client-go库实现文件挂载,当然还可以设置环境变量等等,这里就不多说了,后续dast的策略配置分发就基于这个configmap了。

——————————————————

暂时就先这样,目前还没有涉及到什么新建Services来开放应用到外部,都是直接在自己的集群里面调用的。

参考文章:

https://blog.csdn.net/Dontla/article/details/160908527

https://kubernetes.io/docs/concepts/containers/images/?from=20423&from_column=20423#image-pull-policy