devops-7-kubernetes开发

kubernetes 接口文档

k8s视频教程参考
k8s二次开发本文
k8s自定义资源文章资料
restFul 接口官方
k8sapi接口方式 rest、集群角色token、证书访问
多云管理kuboard

kuboard

  • 安装
docker run -d \
  --restart=unless-stopped \
  --name=kuboard \
  -p 80:80/tcp \
  -p 10081:10081/tcp \
  -e KUBOARD_ENDPOINT="http://内网IP:80" \
  -e KUBOARD_AGENT_SERVER_TCP_PORT="10081" \
  -v /root/kuboard-data:/data \
  eipwork/kuboard:v3

http接口方式

  • kubectl proxy 启动接口允许指定ip访问
  • kubectl proxy --address=‘xx.xx.xx.xx --accept-hosts=’^*$’ --port=8001
  • http://xx.xx.xx.xx:8001/api/v1 接口列表
  • http://11.164.62.250:8001/api/v1/namespaces/default/pods pod列表
  • 主机容器列表 http://xx.xx.xx.xx:8001/api/v1/pods?fieldSelector=spec.nodeName=diagnosis-tool011164062250.na62

gossh

service/ssh.go
通过ssh获取实时数据

package service

import (
	"fmt"
	gossh "golang.org/x/crypto/ssh"
	"net"
)

// 连接信息
type Cli struct {
	user       string
	pwd        string
	addr       string
	client     *gossh.Client
	session    *gossh.Session
	LastResult string
}

// 连接对象
func (c *Cli) Connect() (*Cli, error) {
	config := &gossh.ClientConfig{}
	config.SetDefaults()
	config.User = c.user
	config.Auth = []gossh.AuthMethod{gossh.Password(c.pwd)}
	config.HostKeyCallback = func(hostname string, remote net.Addr, key gossh.PublicKey) error { return nil }
	client, err := gossh.Dial("tcp", c.addr, config)
	if nil != err {
		return c, err
	}
	c.client = client
	return c, nil
}

// 执行shell
func (c Cli) Run(shell string) (string, error) {
	if c.client == nil {
		if _, err := c.Connect(); err != nil {
			return "", err
		}
	}
	session, err := c.client.NewSession()
	if err != nil {
		return "", err
	}
	// 关闭会话
	defer session.Close()
	buf, err := session.CombinedOutput(shell)

	c.LastResult = string(buf)
	return c.LastResult, err
}

func RunSsh() {
	cli := Cli{
		addr: "x.x.x.x",
		user: "xxx",
		pwd:  "xxx",
	}
	// 建立连接对象
	c, _ := cli.Connect()
	// 退出时关闭连接
	defer c.client.Close()
	//res, _ := c.Run("ls")
	res1, _ := c.Run("pwd")
	//fmt.Println(res)
	fmt.Println(res1)
}

前端整合

pod 列表点击登录

pods.vue

<template>

  <a-card>
    <a-table :dataSource="pods" :columns="columns" :customRow="rowClick">
      <template #bodyCell="{ column, text, record }">
        <template v-if="column.dataIndex === 'ip'">
          <a>登录</a>
        </template>
      </template>
    </a-table>
  </a-card>
</template>

<script>
import { getPodsList } from '@/api/manage'
export default {
  data() {
    return {
      res: [],
      pods: [],
      columns: [
        {
          title: 'Pod名称',
          dataIndex: 'name',
          // key: 'name',
          slots: { customRender: 'name' },
        },
        {
          title: '状态',
          dataIndex: 'status',
          key: 'status',
        },
        {
          title: '镜像',
          dataIndex: 'image',
          key: 'image',
        },
        {
          title: 'ip',
          dataIndex: 'ip',
          key: 'ip',
        },
        {
          title: 'podStatus',
          dataIndex: 'podStatus',
          key: 'podStatus',
          // width:30,
        },
        {
          title: '登录',
          dataIndex: 'ssh',
          key: 'ssh',
        },
        // {
        //   title: 'Action',
        //   key: 'action',
        //   slots: { customRender: 'action' },
        // }
      ],
      loadData: parameter => {
        const requestParameters = Object.assign({}, parameter, this.queryParam)
        console.log('loadData request parameters:', requestParameters)
        return getServiceList(requestParameters)
          .then(res => {
            return res.result
          })
      },
    }
  },
  mounted() {
    this.initData()
  },
  methods: {
    initData() {
      // console.log('loadData.parameter', parameter)
      return getPodsList()
        .then(res => {
          this.res = res
          const pods = []
          for (let i in res) {
            const name = res[i].metadata.name
            const status = res[i].status.phase
            const image = res[i].spec.containers[0].image
            let podStatus = {}
            if ("containerStatuses" in res[i].status) {
              podStatus = res[i].status.containerStatuses[0].state
              // if (res[i].status.containerStatuses.length > 0) {
              //   podStatus = res[i].status.containerStatuses.state
              // }

            }

            // const create_time = res[i].metadata.creationTimestamp.subsubstring(0,10)
            const ip = res[i].status.podIP
            pods.push({
              key: i,
              name: name,
              status: status,
              podStatus: JSON.stringify(podStatus),
              image: image,
              ip: ip,
              ssh: "登录"

            })
          }
          this.pods = pods
          console.log("pods", res, pods)
          return res.result
        })
    },
    rowClick(record, index) {
      return {
        on: {
          click: () => {
            console.log('点击跳转:', record)
            this.$router.push({ path: "/k8s/pod-ssh", query: { podname: record.name, namespace: "default" } })
            // const url = `/k8s/pod-ssh?podname=${record.name}&namespace=${"default"}`
            // this.$router.replace(url)
          },

          dblclick: () => {
            console.log('双击了我')
          },
        }
      }
    },
  },

}
</script>

controller

CRD

  • api 的路径
  • kubectl get --raw /
  • 使用最多的 kubectl get -raw /apis/apps/v1 | python -m json.tool
  • kind StatefulSet Deployment
  • GVR=请求路径 GVK=资源实体
  • 通过kubectl proxy
  • kubectl get statefulsets.apps -A
  • curl 127.0.0.1:8001/apis/apps/v1/statefulset| grep name
    在这里插入图片描述

自定义资源

有了自定义资源定义API,开发者将不需要逐一进行Deployment、Service、ConfigMap等步骤,而是创建并关联一些用于表述整个应用程序或者软件服务的对象。除此,我们能使用自定义的高阶对象,并在这些高阶对象的基础上创建底层对象。例如:我们想要一个Backup资源,我们创建它的对象时,就希望通过spec的定义进行日常的备份操作声明,当提交给k8s集群的时候,相关的Deployment、Service资源会被自动创建,很大程度让业务扩展性加大

Informers

事件接口带索引查找功能的内存缓存

  • 下载
  • go get k8s.io/client-go@v0.22.0
  • go get sigs.k8s.io/controller-runtime
  • 更新deployment官方命令
  • kubectl apply -f https://k8s.io/examples/application/deployment.yaml
  • kubectl apply -f nginx-deployment.yaml
  • kubectl edit deployment 编辑副本数量动态更新
  • kubectl delete deployment nginx-deployment
    在这里插入图片描述
package main

import (
	"fmt"
	v1 "k8s.io/api/apps/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/klog/v2"
	"sigs.k8s.io/controller-runtime/pkg/client/config"
	"time"
)

func main() {

	conf, err := config.GetConfig()
	if err != nil {
		panic(err)
		return
	}
	clientSet, err := kubernetes.NewForConfig(conf)
	if err != nil {
		panic(err)
		return
	}
	informerFactory := informers.NewSharedInformerFactory(clientSet, 30*time.Second)
	deployInformer := informerFactory.Apps().V1().Deployments()
	informer := deployInformer.Informer()
	deployLister := deployInformer.Lister()
	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    onAdd,
		UpdateFunc: onUpdate,
		DeleteFunc: onDelete,
	})
	stopper := make(chan struct{})
	defer close(stopper)
	// 启动Informer List & Watch
	informerFactory.Start(stopper)
	// 等待所有的Informer缓存同步
	informerFactory.WaitForCacheSync(stopper)
	deployments, err := deployLister.Deployments("default").List(labels.Everything())
	// 编辑deploy列表
	for index, deploy := range deployments {
		fmt.Printf("%d -> %s\n", index, deploy.Name)
	}
	<-stopper
}

func onAdd(obj interface{}) {
	deploy := obj.(*v1.Deployment)
	klog.Infoln("add a deploy: ", deploy.Name)
}

func onUpdate(old, new interface{}) {
	oldDeploy := old.(*v1.Deployment)
	newDeploy := new.(*v1.Deployment)
	klog.Infoln("update deploy: ", "旧:", newDeploy.Name, oldDeploy.Status.Replicas, "新:", newDeploy.Status.Replicas, "就绪数:", newDeploy.Status.ReadyReplicas)
}

func onDelete(obj interface{}) {
	deploy := obj.(*v1.Deployment)
	klog.Infoln("delete a deploy:", deploy.Name)
}

websocket

官方websocket教程

安装websocket
  • go get github.com/gorilla/websocket
  • 进入pod kubectl exec -it hao-pod /bin/bash
  • 在这里插入图片描述
    package.json
    “dependencies”: {
    “core-js”: “^3.8.3”,
    “vue”: “^3.2.13”,
    “xterm”: “^4.15.0”,
    “xterm-addon-fit”: “^0.5.0”
    },
    vue前端App.vue
<template>
  <div id="app">
    <div id="xterm"/>
  </div>
</template>

<script>
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm';
import { FitAddon } from "xterm-addon-fit";

export default {
  name: 'WebShell',
  data() {
    return {
      // socketURI: 'ws://127.0.0.1:10000/namespace/kube-system/pod/kube-proxy-dw4ww/container/kube-proxy?method=sh'
      socketURI: 'ws://127.0.0.1:3000/namespace/default/pod/hao-pod/container/kube-proxy?method=sh'
    }
  },
  mounted() {
    this.initSocket()
  },
  methods: {
    initTerm() {
      let element = document.getElementById('xterm');
      // 设置了cols或者fitAddon.fit(); 当一行字符超过80个过会替换现在的内容,否则换行
      const term = new Terminal({
        cursorBlink: true, // 关标闪烁
        cursorStyle: "underline", // 光标样式 'block' | 'underline' | 'bar'
        scrollback: 100, // 当行的滚动超过初始值时保留的行视窗,越大回滚能看的内容越多,
        disableStdin: false, //是否应禁用输入
      });
      this.term = term;
      term.prompt = () => {
        term.write("\r\n$ ");
      };
      term.prompt();
      const fitAddon = new FitAddon();
      this.term.loadAddon(fitAddon);
      this.fitAddon = fitAddon;
      term.open(element);
      // 自适应大小(使终端的尺寸和几何尺寸适合于终端容器的尺寸),初始化的时候宽高都是对的
      fitAddon.fit();
      term.focus();
      this.term.onData(data =>  {
          var msg = {type: "input", input: data}
          // this.term.write(data);
        this.socket.send(JSON.stringify(msg));
      });

      window.addEventListener('resize', this.resizeTerm);
    },
    getColsAndRows(element) {
      // 暂时不用
      element = element || document.getElementById('xterm');
      return {
        rows: parseInt((element.clientHeight - 0) / 18),
        cols: 10 // parseInt(element.clientWidth / 8)
      };
    },
    resizeTerm() {
      this.fitAddon.fit();
      this.term.scrollToBottom();
    },
    initSocket() {
      this.socket = new WebSocket(`${this.socketURI}`);
      this.socketOnClose();
      this.socketOnOpen();
      this.socketOnError();
      this.socketOnMessage();
    },
    socketOnOpen() {
      this.socket.onopen = () => {
        // 连接成功后
        this.initTerm()
      }
    },
    socketOnMessage() {
      this.socket.onmessage = (event) => {
        // 接收推送的消息
        this.term.write(event.data.toString());
      }
    },
    socketOnClose() {
      this.socket.onclose = () => {
        console.log('close socket')
      }
    },
    socketOnError() {
      this.socket.onerror = () => {
        console.log('socket error')
      }
    }
  }
}
</script>

<style scoped>
#xterm {
  padding: 15px 0;
}
</style>

router/router.go

package router

import (
	"gin-client-go/pkg/apis"
	"github.com/gin-gonic/gin"
)

func InitRouter(r *gin.Engine) {
	r.GET("/ping", apis.Ping)
	r.GET("namespaces", apis.GetNamespaces)
	r.GET("/namespace/:namespaceName/pods", apis.GetPods)
	r.GET("/namespace/:namespaceName/pod/:podName/container/:container", apis.ExecContainer)
	r.GET("/ws", func(c *gin.Context) { apis.Ws(c.Writer, c.Request) })
}

apis/pod.go

package apis

import (
	"fmt"
	"gin-client-go/pkg/service"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"net/http"
)

func GetPods(c *gin.Context) {
	namespaceName := c.Param("namespaceName")
	pods, err := service.GetPods(namespaceName)
	if err != nil {
		c.JSON(http.StatusInternalServerError, err.Error())
	}
	c.JSON(http.StatusOK, pods)
}

func ExecContainer(c *gin.Context) {
	namespaceName := c.Param("namespaceName")
	podName := c.Param("podName")
	containerName := c.Param("containerName")
	method := c.DefaultQuery("action", "sh")
	err := service.WebSSH(namespaceName, podName, containerName, method, c.Writer, c.Request)
	if err != nil {
		c.JSON(http.StatusInternalServerError, err.Error())
	}
}

var wsupgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func Ws(w http.ResponseWriter, r *http.Request) {
	conn, err := wsupgrader.Upgrade(w, r, nil)
	if err != nil {
		fmt.Println("Failed to set websocket upgrade: %+v", err)
		return
	}

	for {
		t, msg, err := conn.ReadMessage()
		if err != nil {
			break
		}
		fmt.Println("获取到消息", msg)
		conn.WriteMessage(t, msg)
	}
}


service/pod.go

package service

import (
	"context"
	"errors"
	"fmt"
	"gin-client-go/pkg/client"
	"github.com/gorilla/websocket"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/json"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/remotecommand"
	"k8s.io/klog/v2"
	"net/http"
	"sync"
)

func GetPods(namespaceName string) ([]v1.Pod, error) {
	ctx := context.Background()
	clientSet, err := client.GetK8SClientSet()
	if err != nil {
		klog.Fatal(err)
		return nil, err
	}

	list, err := clientSet.CoreV1().Pods(namespaceName).List(ctx, metav1.ListOptions{})
	if err != nil {
		klog.Fatal(err)
		return nil, err
	}
	return list.Items, nil
}

type WsMessage struct {
	MessageType int
	Data        []byte
}
type WsConnection struct {
	wsSocket *websocket.Conn
	//读取的通道
	inChan chan *WsMessage
	//写入通道
	outChan chan *WsMessage
	//为了防止channel重复关闭,加一下锁
	mutex    sync.Mutex
	isClosed bool
	//通道关闭后通知的标识
	closeChan chan byte
}

// 关闭
func (wsConn *WsConnection) WsClose() {
	err := wsConn.wsSocket.Close()
	if err != nil {
		klog.Fatal(err)
		return
	}
	//为了防止重复关闭,加锁
	wsConn.mutex.Lock()
	defer wsConn.mutex.Unlock()
	if !wsConn.isClosed {
		wsConn.isClosed = true
		close(wsConn.closeChan)
	}
}

// 发送的携程
func (wsConn *WsConnection) wsReadLoop() {
	var (
		msgType int
		data    []byte
		msg     *WsMessage
		err     error
	)
	for {
		msgType, data, err = wsConn.wsSocket.ReadMessage()
		//if msgType, data, err = wsConn.wsSocket.ReadMessage(); err != nil {
		if err != nil {
			klog.Errorln(err)
			goto ERROR
		}
		msg = &WsMessage{
			MessageType: msgType,
			Data:        data,
		}
		klog.Infof("读取消息===》", string(data))
		select {
		case wsConn.inChan <- msg:
		//处理websocket已经关闭
		case <-wsConn.closeChan:
			goto CLOSE
		}
	}
ERROR:
	wsConn.WsClose()
CLOSE:
}

// 写入
func (wsConn *WsConnection) wsWriteLoop() {
	klog.Info("777779999999999")
	var (
		msg *WsMessage
		err error
	)
	for {
		select {
		case msg = <-wsConn.outChan:
			klog.Infof("写入消息===>", msg.Data)
			err = wsConn.wsSocket.WriteMessage(msg.MessageType, msg.Data)
			if err != nil {
				klog.Info("9999999999")
				goto ERROR
			}
		//读取完消息,写给websocket
		case <-wsConn.closeChan:
			goto CLOSED
		}
	}
ERROR:
	wsConn.WsClose()
CLOSED:
}

// 发送消息
func (wsConn *WsConnection) WsWrite(messageType int, data []byte) (err error) {
	select {
	//写数据
	case wsConn.outChan <- &WsMessage{MessageType: messageType, Data: data}:
		return
	//遇到关闭的情况
	case <-wsConn.closeChan:
		err = errors.New("websocket 通道关闭")
	}
	return
}

func (wsConn *WsConnection) WsRead() (msg *WsMessage, err error) {
	select {
	case msg = <-wsConn.inChan:
		klog.Infof("WsRead", msg)
		return
	case <-wsConn.closeChan:
		err = errors.New("websocket 通道关闭")

	}
	return
}

// 处理websockert的跨域请求
var wsUpgrade = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

type streamHander struct {
	WsConnection *WsConnection
	resizeEvent  chan remotecommand.TerminalSize
}

func (handler *streamHander) Write(p []byte) (size int, err error) {
	copyData := make([]byte, len(p))
	copy(copyData, p)
	size = len(p)
	err = handler.WsConnection.WsWrite(websocket.TextMessage, copyData)
	return
}

type xtermMessage struct {
	MsgType string `json:"type"`
	Input   string `json:"input"`
	Rows    uint16 `json:"rows"`
	Cols    uint16 `json:"cols"`
}

func (handler *streamHander) Read(p []byte) (size int, err error) {
	var (
		xtermMsg xtermMessage
		msg      *WsMessage
	)
	if msg, err = handler.WsConnection.WsRead(); err != nil {
		klog.Fatal(err)
		return
	}
	//解析消息
	if err = json.Unmarshal(msg.Data, &xtermMsg); err != nil {
		return
	}
	//终端调整大小适配
	if xtermMsg.MsgType == "resize" {
		handler.resizeEvent <- remotecommand.TerminalSize{Width: xtermMsg.Cols, Height: xtermMsg.Rows}
	} else if xtermMsg.MsgType == "input" {
		size = len(xtermMsg.Input)
		copy(p, xtermMsg.Input)
	}
	return
}

func (handler *streamHander) Next() (size *remotecommand.TerminalSize) {
	ret := <-handler.resizeEvent
	size = &ret
	return
}

func WebSSH(namespaceName, podName, containerName, method string, resp http.ResponseWriter, req *http.Request) error {
	var (
		err      error
		executor remotecommand.Executor
		handler  *streamHander
		wsConn   *WsConnection
	)
	config, err := client.GetRestConfig()
	if err != nil {
		klog.Fatal(err)
		return err
	}
	clientSet, err := client.GetK8SClientSet()
	if err != nil {
		klog.Fatal(err)
		return err
	}
	reqSSH := clientSet.CoreV1().RESTClient().Post().Resource("pods").Name(podName).
		Namespace(namespaceName).SubResource("exec").
		VersionedParams(&v1.PodExecOptions{
			Container: containerName,
			Command:   []string{method},
			Stderr:    true,
			Stdout:    true,
			Stdin:     true,
			TTY:       true,
		}, scheme.ParameterCodec)
	if executor, err = remotecommand.NewSPDYExecutor(config, "POST", reqSSH.URL()); err != nil {
		klog.Errorln(err)
		fmt.Println(executor)
		return err
	}
	klog.Infof("获取pod通道===>", executor)

	if wsConn, err = InitWebsocket(resp, req); err != nil {
		fmt.Println(wsConn)
		return err
	}
	handler = &streamHander{WsConnection: wsConn, resizeEvent: make(chan remotecommand.TerminalSize)}
	if err = executor.Stream(remotecommand.StreamOptions{
		Stdin:             handler,
		Stdout:            handler,
		Stderr:            handler,
		TerminalSizeQueue: handler,
		Tty:               true,
	}); err != nil {
		goto END
	}
	return err
END:
	klog.Errorln(err)
	wsConn.WsClose()
	return err
}

// 创建websocket init连接
func InitWebsocket(resp http.ResponseWriter, req *http.Request) (wsConn *WsConnection, err error) {
	klog.Infof("初始化通道===>", resp, req)
	var (
		wsSocket *websocket.Conn
	)
	if wsSocket, err = wsUpgrade.Upgrade(resp, req, nil); err != nil {
		klog.Errorln(err)
		return
	}

	wsConn = &WsConnection{
		wsSocket:  wsSocket,
		inChan:    make(chan *WsMessage, 1000),
		outChan:   make(chan *WsMessage, 1000),
		closeChan: make(chan byte),
		isClosed:  false,
	}
	//读取携程
	go wsConn.wsReadLoop()
	//写携程
	go wsConn.wsWriteLoop()
	return
}


基于gin

注册路由 router/router.go
	r.GET("namespaces", apis.GetNamespaces)
新建kubernetes
  • 下载包 go get k8s.io/client-go/rest

新建接口apis/namespace.go

package apis

import (
	"gin-client-go/pkg/service"
	"github.com/gin-gonic/gin"
	"net/http"
)

func GetNamespaces(c *gin.Context) {
	namespaces, err := service.GetNamespaces()
	if err != nil {
		c.JSON(http.StatusInternalServerError, err.Error())
	}
	c.JSON(http.StatusOK, namespaces)
}

新建服务获取service/namespaces.go

package service

import (
	"context"
	"gin-client-go/pkg/client"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/klog/v2"
)

func GetNamespaces() ([]v1.Namespace, error) {
	ctx := context.Background()
	clientSet, err := client.GetK8SClientSet()
	if err != nil {
		klog.Fatal(err)
		return nil, err
	}

	namespaceList, err := clientSet.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
	if err != nil {
		klog.Fatal(err)
		return nil, err
	}
	return namespaceList.Items, nil
}

新建 client/kubernetes.go 多去配置文件

package client

import (
	"flag"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	"k8s.io/klog/v2"
	"path/filepath"
)

func GetK8SClientSet() (*kubernetes.Clientset, error) {
	config, err := GetRestConfig()
	if err != nil {
		klog.Fatal(err)
		return nil, err
	}
	clientSet, err := kubernetes.NewForConfig(config)
	if err != nil {
		klog.Fatal(err)
		return nil, err
	}
	return clientSet, nil
}

func GetRestConfig() (config *rest.Config, err error) {
	var kubeConfig *string
	if home := homedir.HomeDir(); home != "" {
		kubeConfig = flag.String("kubeConfig", filepath.Join(home, ".kube", "config"), "kube config")
	} else {
		klog.Fatal("未找到kubernetes配置文件")
		return
	}
	flag.Parse()
	config, err = clientcmd.BuildConfigFromFlags("", *kubeConfig)
	if err != nil {
		klog.Fatal(err)
		return
	}
	return
}

解决重复刷新问题,单例模式

package client

import (
	"flag"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	"k8s.io/klog/v2"
	"path/filepath"
	"sync"
)

// 单例模式解决重复刷新报错问题
var onceClient sync.Once
var onceConfig sync.Once
var KubeConfig *rest.Config
var KubeClientSet *kubernetes.Clientset

func GetK8SClientSet() (*kubernetes.Clientset, error) {
	onceClient.Do(func() {
		config, err := GetRestConfig()
		if err != nil {
			klog.Fatal(err)
			return
		}
		KubeClientSet, err = kubernetes.NewForConfig(config)
		if err != nil {
			klog.Fatal(err)
			return
		}
	})
	return KubeClientSet, nil
}

func GetRestConfig() (config *rest.Config, err error) {
	onceConfig.Do(func() {
		var kubeConfig *string
		if home := homedir.HomeDir(); home != "" {
			kubeConfig = flag.String("kubeConfig", filepath.Join(home, ".kube", "config"), "kube config")
		} else {
			klog.Fatal("未找到kubernetes配置文件")
			return
		}
		flag.Parse()
		KubeConfig, err = clientcmd.BuildConfigFromFlags("", *kubeConfig)
		if err != nil {
			klog.Fatal(err)
			return
		}
		return

	})
	return KubeConfig, nil

}

声明跨域的头
在这里插入图片描述
在这里插入图片描述
路由注册
在这里插入图片描述

新建路由

新建路由 common.go

package apis

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func Ping(c *gin.Context) {
	c.JSON(http.StatusOK, "Pong")
}

router.go

package router

import (
	"gin-client-go/pkg/apis"
	"github.com/gin-gonic/gin"
)

func InitRouter(r *gin.Engine) {
	r.GET("/ping", apis.Ping)
}

main.go中注册路由

	//注册路由
	router.InitRouter(engine)

在这里插入图片描述

新建目录配置
  • 安装包 go get gopkg.in/yaml.v2
  • go mode tidy
  • 安装gin
  • go get github.com/gin-gonic/gin

key.go

package config

type KeyName string

const (
	ServerName KeyName = "server_name"
	ServerHost KeyName = "server_host"
	ServerPort KeyName = "server_port"
)

config.go

package config

import (
	"fmt"
	"gopkg.in/yaml.v2"
	"k8s.io/klog/v2"
	"os"
	"strconv"
)

var keyMap map[KeyName]string

type Config struct {
	Server Server
}

type Server struct {
	Name string `yaml:"name"`
	Host string `yaml:"host"`
	Port string `yaml:"port"`
}

//服务启动读取配置文件

func init() {
	var config Config
	yamlFile, err := os.ReadFile("./gin-client-go.yaml")
	if err != nil {
		klog.Fatal(err)
		return
	}
	err = yaml.Unmarshal(yamlFile, &config)
	if err != nil {
		klog.Fatal(err)
		return
	}
	fmt.Println("获取yaml报错:", err)
	fmt.Println("获取到配置信息", config.Server.Name, config.Server.Host, config.Server.Port)
	keyMap = make(map[KeyName]string)
	keyMap[ServerName] = config.Server.Name
	keyMap[ServerHost] = config.Server.Host
	keyMap[ServerPort] = config.Server.Port
}

// 对外获取
func GetString(name KeyName) string {
	fmt.Println(name, keyMap[name])
	return keyMap[name]
}

func GetInt(name KeyName) int {
	intStr := keyMap[name]
	if intStr == "" {
		klog.Fatal("未读取到配置数据", name)
		return -1
	}
	v, err := strconv.Atoi(intStr)
	if err != nil {
		klog.Fatal(err)
		return -1
	}
	return v
}

gin-client-go.yaml

server:
    name: gin-client-go
    host: 0.0.0.0
    port: 3000

main.go

package main

import (
	"fmt"
	"gin-client-go/pkg/config"
	"github.com/gin-gonic/gin"
	"k8s.io/klog/v2"
)

func main() {
	engine := gin.Default()
	gin.SetMode(gin.DebugMode)
	err := engine.Run(fmt.Sprintf("%s:%d", config.GetString(config.ServerHost), config.GetInt(config.ServerPort)))
	if err != nil {
		klog.Fatal(err)
		return
	}

}

新建yaml

.gin-client-go.yaml

server:
  name:gin-client-go
  host:0.0.0.0
  port:8888

基于client-go

k8s二次开发b站视频

配置环境

  • 集群配置文件
    cat /etc/kubernetes/admin.conf
    cat ~/.kube/config
    放在mac开发环境

代码

  • 创建目录
mkdir -p ~/go/src/k8s-dev/gin-client-go
touch go.mod
vi go.mod
module gin-client-go
go 1.19
touch main.go
go get k8s.io/client-go
go mode tidy


  • main.go
package main

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

func main() {
	var kubeConfig *string
	ctx := context.Background()
	if home := homedir.HomeDir(); home != "" {
		kubeConfig = flag.String("kubeConfig", filepath.Join(home, ".kube", "config"), "kube config")
	} else {
		kubeConfig = flag.String("kubeConfig", "", "kube config")
	}
	flag.Parse()
	config, err := clientcmd.BuildConfigFromFlags("", *kubeConfig)
	if err != nil {
		panic(err)
		return
	}
	clientSet, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err)
		return
	}
	namespaceList, err := clientSet.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
	if err != nil {
		panic(err)
		return
	}
	namespaces := namespaceList.Items
	for _, namespace := range namespaces {
		fmt.Println(namespace.Name, namespace.CreationTimestamp, namespace.Status.Phase)
	}
	nodeList, err := clientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
	if err != nil {
		panic(err)
		return
	}
	nodes := nodeList.Items
	for _, node := range nodes {
		fmt.Println("node节点", node.Name)
	}
}


输出

default
kube-flannel
kube-node-lease
kube-public
kube-system
kubernetes-dashboard

验证 kubectl get ns

helm部署

下载地址官网
国内helm软件下载地址
应用送搜

wget https://get.helm.sh/helm-v3.10.2-linux-amd64.tar.gz
几乎无法下载
wget https://mirrors.huaweicloud.com/helm/v3.1.2/helm-v3.1.2-linux-amd64.tar.gz  
tar -zxvf 
cp helm /usr/bin/
添加数据源
helm repo add bitnami https://charts.bitnami.com/bitnami
安装 dashboard
helm install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --namespace kube-system

https://artifacthub.io/packages/helm/k8s-dashboard/kubernetes-dashboard
搜索

helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
安装
helm install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard



NAME: kubernetes-dashboard
LAST DEPLOYED: Fri Nov 18 17:07:18 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
*********************************************************************************
*** PLEASE BE PATIENT: kubernetes-dashboard may take a few minutes to install ***
*********************************************************************************

Get the Kubernetes Dashboard URL by running:
  export POD_NAME=$(kubectl get pods -n default -l "app.kubernetes.io/name=kubernetes-dashboard,app.kubernetes.io/instance=kubernetes-dashboard" -o jsonpath="{.items[0].metadata.name}")
  echo https://127.0.0.1:8443/
  kubectl -n default port-forward $POD_NAME 8443:8443

删除 `helm delete kubernetes-dashboard`
kubectl get svc
NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)         AGE

kubectl edit svc kubernetes-dashboard
修改svc 网络类型
type:ClusterIP
type:NodePort
kubectl get svc 访问node节点ip

ls /etc/kubernetes/pki/ca.crt
管理证书
导入证书仍不被信任
用火狐浏览器 或者直接敲击 thisisunsafe
登录认证token
kubectl -n kube-system get secret | grep kubernetes-dashboard-token
kubectl describe secret kubernetes-dashb`在这里插入代码片`oard-token-xxxxx

本机的
kubectl get ns
kubectl -n kubernetes-dashboard get secret
kubectl -n kubernetes-dashboard describe secret kubernetes-dashboard-token-xxxx

kubectl -n kube-system get secret
kubectl -n kube-system  describe secret ttl-controller-token-ds6f4
kubectl describe secret xxxtoken

kubernetes 学习

cd /var/lib/kubelet/
cd /etc/kubernetes/
admin.conf  controller-manager.conf  kubelet.conf  manifests  pki  scheduler.conf

副本扩容
kubectl scale --replicas=3 deployment/nginx-deployment
kubectl edit deployment nginx-deployment

kubectl  expose  --help
将本机的 
kubectl expose deployment nginx-deployment --port=3000 --target-port=80
kubectl describe svc nginx-deployment

kubectl get pod -o wide

ipvsadm -Ln

定义资源清单

名称空间 集群级别role 元数据类型HPA
集群资源: namespace、node、role角色、ClusterRole、RoleBinding、ClusterRoleBinding
元数据资源:HPA、PodTemplate、LimitRange
p17

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
    version: v1
spec:
  containers:
  - name: app
    image: nginx:v1
  - name: test
    image: mysql:v1

kubectl apply -f pod.yaml
kubectl describe pod myapp-pod
kubectl log myapp-pod -c test
kubectl logs myapp-pod
kubectl delete pod myapp-pod
kubectl create -f pod.yaml
kubectl delete pod --all
kubectl delete deployment --all

pod 声明周期
initc
spec.containers.command: ['sh','-c','echo the app is running && sleep 3600']
spec.containers.initContainers
- name:init-myservice
  image:busybox
  command: [' sh','-c','until nslookup myservice;do echo waiting for myservice; sleep 2; done;'] 
  # 当until 条件为真时结束循环
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值