Skip to content

Index

一步步实现微服务权限管理系统(3)

前言

  • 前端参考了很多框架,可谓百花齐放,但很多封装过剩,不利于学习和应用,最终我选择了 [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin)
  • 后端我将使用 go-zero 来带领大家一步步实现一个权限管理系统
  • 本系列项目存放在 purezeroadmin 中,每一部分我都将打tag,并保证每个tag能正常运行。请多点赞和评论。
  • 后面示例均为 purezeroadmin 项目为例,你们可以根据需要自建工程来进行试验。均采用vscode进行试验。
  • go-zero 常用命令我将放入其对应的 makefile 文件中。

前端所有请求接口前缀加api

修改对应请求文件

  1. src/api/user.ts
/** 登录 */
export const getLogin = (data?: object) => {
  return http.request<UserResult>("post", "/api/login", { data });
};

/** 刷新`token` */
export const refreshTokenApi = (data?: object) => {
  return http.request<RefreshTokenResult>("post", "/api/refresh-token", { data });
};
  1. src/api/routes.ts
export const getAsyncRoutes = () => {
  return http.request<Result>("get", "/api/get-async-routes");
};

修改本地跨域代理

  • vite.config.ts
// 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
proxy: {
'/api': {
    target: 'http://localhost:8888',
    changeOrigin: true,
}
},

后端接口前缀统一为/api

修改user-api/api/user.api相关信息

...
service user-api {
    @doc "用户登录"
    @handler userLogin
    post /api/login (UserLoginReq) returns (UserLoginResp)
}
...
service user-api {
    @doc "刷新token"
    @handler UserRefreshToken
    post /api/refresh-token (UserRefreshTokenReq) returns (UserRefreshTokenResp)
}
...
service user-api {
    @doc "获取路由"
    @handler userRouter
    get /api/get-async-routes (UserRouterReq) returns (UserRouterResp)
}

修改后重新生成代码

测试

完成以上可进行测试

tag版本

purezeroadmin 项目下

git checkout v1.2.0

接下来

现在有个问题,就是后端成功和失败返回的格式不统一,如用户登陆,成功返回 types.UserLoginResp, 而失败返回 error, 这是不规范的,下一节将解决相关问题

一步步实现微服务权限管理系统(2)

前言

  • 前端参考了很多框架,可谓百花齐放,但很多封装过剩,不利于学习和应用,最终我选择了 [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin)
  • 后端我将使用 go-zero 来带领大家一步步实现一个权限管理系统
  • 本系列项目存放在 purezeroadmin 中,每一部分我都将打tag,并保证每个tag能正常运行。请多点赞和评论。
  • 后面示例均为 purezeroadmin 项目为例,你们可以根据需要自建工程来进行试验。均采用vscode进行试验。
  • go-zero 常用命令我将放入其对应的 makefile 文件中。

前端相关接口参考

前端模拟的接口在 mock 文件夹中,后端可参照修改

后端修改

登陆接口

  1. 公共api创建 前端返回接口均为 {success: true, data: xxx}, 所以创建公共api `
cat base-api/base.api

api:

type Base {
    Success bool `json:"success"`
}
  1. 依照前章示例,创建 user.api 文件
cat `user-api/api/user.api`

api:

syntax = "v1"

info (
    title: "q9090960bnb3"
    desc:  "haha"
    date:  "2024-10-31"
)

import "../../base-api/base.api"

type UserLoginData {
    Avatar       string   `json:"avatar"`
    Username     string   `json:"username"`
    Nickname     string   `json:"nickname"`
    Roles        []string `json:"roles"`
    Permissions  []string `json:"permissions"`
    AccessToken  string   `json:"accessToken"`
    RefreshToken string   `json:"refreshToken"`
    Expires      string   `json:"expires"`
}

type (
    UserLoginReq {
        UserName string `json:"username"`
        Password string `json:"password"`
    }
    UserLoginResp {
        Base
        Data UserLoginData `json:"data"`
    }
)

service user-api {
    @doc "用户登录"
    @handler userLogin
    post /login (UserLoginReq) returns (UserLoginResp)
}
  1. 生成代码
goctl api go --api user-api/api/user.api --dir user-api
  1. 参考前端mock数据,修改 user-api/internal/logic/userloginlogic.go 中的函数
func (l *UserLoginLogic) UserLogin(req *types.UserLoginReq) (resp *types.UserLoginResp, err error) {
    if req.UserName == "admin" {
        return &types.UserLoginResp{
            Base: types.Base{
                Success: true,
            },
            Data: types.UserLoginData{
                Avatar:       "https://avatars.githubusercontent.com/u/44761321",
                Username:     "admin",
                Nickname:     "小铭",
                Roles:        []string{"admin"},
                Permissions:  []string{"*:*:*"},
                AccessToken:  "eyJhbGciOiJIUzUxMiJ9.admin",
                RefreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
                Expires:      "2030/10/30 00:00:00",
            },
        }, nil
    }

    return &types.UserLoginResp{
        Base: types.Base{
            Success: true,
        },
        Data: types.UserLoginData{
            Avatar:       "https://avatars.githubusercontent.com/u/52823142",
            Username:     "common",
            Nickname:     "小林",
            Roles:        []string{"common"},
            Permissions:  []string{"permission:btn:add", "permission:btn:edit"},
            AccessToken:  "eyJhbGciOiJIUzUxMiJ9.common",
            RefreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh",
            Expires:      "2030/10/30 00:00:00",
        },
    }, nil
}

修改刷新token接口

  1. user.api 中添加
type UserRefreshTokenData {
    AccessToken  string `json:"accessToken"`
    RefreshToken string `json:"refreshToken"`
    Expires      string `json:"expires"`
}

type (
    UserRefreshTokenReq {
        RefreshToken string `json:"refreshToken"`
    }
    UserRefreshTokenResp {
        Base
        Data UserRefreshTokenData `json:"data"`
    }
)

service user-api {
    @doc "用户登录"
    @handler UserRefreshToken
    post /refresh-token (UserRefreshTokenReq) returns (UserRefreshTokenResp)
}
  1. 参考前端mock数据,修改 user-api/internal/logic/userrefreshtokenlogic.go 中的函数
func (l *UserRefreshTokenLogic) UserRefreshToken(req *types.UserRefreshTokenReq) (resp *types.UserRefreshTokenResp, err error) {
    if req.RefreshToken != "" {
        return &types.UserRefreshTokenResp{
            Base: types.Base{
                Success: true,
            },
            Data: types.UserRefreshTokenData{
                AccessToken:  "",
                RefreshToken: "",
                Expires:      "",
            },
        }, nil
    }
    return &types.UserRefreshTokenResp{}, nil
}

修改异步获取路由接口

  1. user.api 中添加
type RouterData {
    Path      string       `json:"path"`
    Name      string       `json:"name,omitempty"`
    Component string       `json:"component,omitemty"`
    Meta      Meta         `json:"meta"`
    Children  []RouterData `json:"children,omitempty"`
}

type Meta {
    Title string   `json:"title"`
    Icon  string   `json:"icon,omitempty"`
    Rank  int64    `json:"rank,omitempty"`
    Roles []string `json:"roles,omitempty"`
    Auths []string `json:"auths,omitempty"`
}

type (
    UserRouterReq  {}
    UserRouterResp {
        Base
        Data []RouterData `json:"data"`
    }
)

service user-api {
    @doc "用户登出"
    @handler userRouter
    get /get-async-routes (UserRouterReq) returns (UserRouterResp)
}
  1. 参考前端mock数据,修改 user-api/internal/logic/userrouterlogic.go 中的函数
func (l *UserRouterLogic) UserRouter(req *types.UserRouterReq) (resp *types.UserRouterResp, err error) {
    return &types.UserRouterResp{
        Base: types.Base{
            Success: true,
        },
        Data: []types.RouterData{
            {
                Path: "/permission",
                Meta: types.Meta{
                    Title: "权限管理",
                    Icon:  "ep:lollipop",
                    Rank:  10,
                },
                Children: []types.RouterData{
                    {
                        Path: "/permission/page/index",
                        Name: "PermissionPage",
                        Meta: types.Meta{
                            Title: "页面权限",
                            Roles: []string{"admin", "common"},
                        },
                    },
                    {
                        Path: "/permission/button",
                        Meta: types.Meta{
                            Title: "按钮权限",
                            Roles: []string{"admin", "common"},
                        },
                        Children: []types.RouterData{
                            {
                                Path:      "/permission/button/router",
                                Component: "permission/button/index",
                                Name:      "PermissionButtonRouter",
                                Meta: types.Meta{
                                    Title: "路由返回按钮权限",
                                    Auths: []string{
                                        "permission:btn:add",
                                        "permission:btn:edit",
                                        "permission:btn:delete",
                                    },
                                },
                            },
                            {
                                Path:      "/permission/button/login",
                                Component: "permission/button/perms",
                                Name:      "PermissionButtonLogin",
                                Meta: types.Meta{
                                    Title: "登录返回按钮权限",
                                },
                            },
                        },
                    },
                },
            },
        },
    }, nil
}

前端取消mock

参考 如何关闭mock

前端配置端口转发

vite.config.ts 中配置转发

// 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
proxy: {
"^/(login|refresh-token|get-async-routes)": {
    target: "http://localhost:8888",
    changeOrigin: true,
},
},

启动测试

  1. 启动后端服务
# vue-pure-admin工程下执行 cd back-end/user-api
❯ go run user.go
Starting server at 0.0.0.0:8888...
  1. 启动前端服务
# vue-pure-admin工程下执行 cd front-end
❯ pnpm dev

tag版本

purezeroadmin 项目下

git checkout v1.1.0

接下来

目前,前端请求路由太自由了,最好采用统一前缀去访问后端系统

一步步实现微服务权限管理系统(1)

前言

  • 前端参考了很多框架,可谓百花齐放,但很多封装过剩,不利于学习和应用,最终我选择了 [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin)
  • 后端我将使用 go-zero 来带领大家一步步实现一个权限管理系统
  • 本系列项目存放在 purezeroadmin 中,每一部分我都将打tag,并保证每个tag能正常运行。
  • 后面示例均为 purezeroadmin 项目为例,你们可以根据需要自建工程来进行试验。均采用vscode进行试验。
  • go-zero 常用命令我将放入其对应的 makefile 文件中。

搭建环境

前端环境

  1. 参考 vue-pure-admin 官网,下载对应工具包
 cd vue-pure-admin
❯ pure create front-end
? 请输入项目名称 front-end
? 选择一个代码托管平台下载模板 Gitee
? 请选择模板类型 thin
  1. 运行,打开浏览器观测成功
# vue-pure-admin工程下执行 cd front-end
# 删除git仓库,外部工程自己git管理 rm -rf .git  pnpm i
❯ pnpm dev

后端环境

  1. 参考 go-zero 官网,下载对应工具包

  2. 创建工程

# vue-pure-admin工程下执行 mkdir back-end
❯ cd back-end
❯ go mod init backend
  1. 创建测试api
# back-end工程下执行 mkdir -p hello-api/api

创建测试 api 文件 hello.api

syntax = "v1"

info (
    title: "q9090960bnb3"
    desc:  "hello"
    date:  "2024-11-01"
)

type (
    HelloReq {
        World string `form:"world"`
    }
    HelloResp {
        HelloWorld string `json:"hello_world"`
    }
)

service hello-api {
    @doc "你好,世界"
    @handler helloInfo
    get /hello (HelloReq) returns (HelloResp)
}
  1. 生成 api 项目代码
# back-end工程下执行 goctl api go --api hello-api/api/hello.api --dir hello-api
  1. 修改返回逻辑
# back-end工程下执行
cd hello-api/internal/logic

修改 helloinfologic.goHelloInfo 函数

func (l *HelloInfoLogic) HelloInfo(req *types.HelloReq) (resp *types.HelloResp, err error) {
    return &types.HelloResp{
        HelloWorld: "hello " + req.World,
    }, nil
}
  1. 运行查看效果
# back-end工程下执行 cd hello-api
❯ go run hello.go
Starting server at 0.0.0.0:8888...
{"@timestamp":"2024-11-01T11:17:36.574+08:00","caller":"stat/usage.go:61","content":"CPU: 77m, MEMORY: Alloc=1.7Mi, TotalAlloc=3.6Mi, Sys=12.5Mi, NumGC=1","level":"stat"}
...

curl 测试

 curl "http://localhost:8888/hello?world=purezeroadmin"
{"hello_world":"hello purezeroadmin"}#  

tag版本

purezeroadmin 项目下

git checkout v1.0.0

接下来

front-end 中的mock数据替换到 back-end 中了

Podman容器概述

Podman是一种用于管理和运行容器的工具,类似于Docker。它可以帮助你在Linux系统上创 建、管理和运行容器,让你的应用程序更加轻便和可移植。与Docker相比,Podman提供了更强的隔 离和安全性,支持在非特权用户下运行容器,并提供与Docker·CLI兼容的命令行接口,让Docker用户 可以很容易地使用Podman。Podman可以通过简单的命令行操作来启动、停止和检查容器状态,并支 持使用Dockerfile来构建容器镜像。总的来说,Podman是一个强大而灵活的工具,可以帮助你更方便 地管理和部署容器化应用程序。

podman 与 docker 区别

与Docker相比,Podman具有以下一些不同之处: 1、Podman不需要运行Docker守护程序,因此可以在不同的Linux主机上运行,从而提高了可 移植性。 2、Podman容器不需要特权用户或管理员权限即可运行,这使得非特权用户可以在Linux系统上 运行容器,从而提高了安全性。 3、Podman·CLI与Docker·CLI兼容,因此Docker用户可以很容易地转换到Podman容器环境 中。

podman 进程

使用Podman之后我们不需要管理和Docker守护进程一样的守护进程,Podman也同样支持 Docker命令,他们的镜像也是兼容的。 Podman·官网地址: https://podman.io/ Podman·项目地址:https://github.com/containers/libpod

podman的安装和使用

  • 安装
$ yum install  -y podman --allowerasing
  • 简单使用
$ podman search busybox
$ podman pull busybox
# 查看镜像
$ podman images
# 删除镜像
$ podman rmi -f af
# 加载镜像
$ podman load -i nginx.tar
# 运行容器
$ podman run -it busybox
# 运行并进入容器
$ podman run -it busybox sh
# 删除容器
$ podman rm -f d7
# 查看容器
$ podman ps 
  • exam
$ podman run --name nginx-v1 --restart=always -itd -p 9090:80 nginx

设置podman 容器实例开机自动运行

$ podman run --name web86 --restart=always -itd -p 86:80 nginx
# 进入做启动脚本目录
$ cd /usr/lib/systemd/system
# 
$ podman generate systemd --name web86 --files

DEPRECATED command:
It is recommended to use Quadlets for running containers and pods under systemd.

Please refer to podman-systemd.unit(5) for details.
WARN[0000] Container 2a874d250f134ffa1b7e4a53f65fceae687a496e303347e95983f7337125f494 has restart policy "always" which can lead to issues on shutdown: consider recreating the container without a restart policy and use systemd's restart mechanism instead 
/usr/lib/systemd/system/container-web86.service
# 重启测试
$ systemctl daemon-reload
$ systemctl enable container-web86.service
$ reboot -f

Containerd容器介绍

官方文档: https://containerd.io

概述

Containerd是一个工业级标准的容器运行时,它由Docker公司开发并贡献给了Cloud·Native Computing·Foundation(CNCF)。它提供了基本的容器管理功能,包括容器镜像的传输和存储、容器 的执行和管理、容器的网络管理等。与Docker相比,Containerd更加轻量化和模块化,可以更好地与 Kubernetes等容器编排系统集成。

containerd只能运行和拉取镜像,不能制作镜像

发展

  • 从Docker 1.11版本开始,Docker公司将Containerd从Docker中分离出来,并将其作为一个 独立的开源项目提交给CNCF进行维护。这是因为Docker作为一个完整的容器平台,包含了很多不必 要的功能,而且代码比较庞大,难以维护。Containerd则是一个更加专注于容器管理的运行时,可以更 好地满足云原生应用的需求。

  • K8s1.24版本开始,不在支持docker,为什么? 第一,Docker·的发展方向与·Kubernetes·并不完全一致,Docker·更注重整个生态系统,而 Kubernetes·更关注以容器为中心。 第二,containerd·是一个工业级标准的容器运行时,它可以更好地与·Kubernetes·集成,并提供 更好的性能和可用性,更轻量。( 第三,它不符合Kubernetes的CRl(容器运行时接口)规范,Docker不符合Kubernetes·CRI 规范的原因有几个: 1)过于庞大:Docker是一个庞大的软件包,其中包含了大量的功能,这些功能并不都是 Kubernetes需要的。这使得Docker的部署和管理变得复杂,也不利于轻量级容器的运行。 2)APl不稳定:Docker的API并不是为Kubernetes设计的,因此在Kubernetes环境中使用 Docker会出现API不稳定的情况,这会导致兼容性问题和不必要的麻烦。 3)安全性问题:Docker的安全模型并不是为Kubernetes设计的,这可能会导致在Kubernetes 环境中运行容器时出现不必要的安全风险。

containerd安装与配置

$ yum install containerd.io-1.6.22 --allowerasing -y
# 查看版本 
$ ctr version
  • 生成配置文件
containerd config default > /etc/containerd/config1.toml
  • 修改 containerd 配置文件 /etc/containerd/config.toml
...
SystemdCgroup = true
...
[plugins."io.containerd.grpc.v1.cri".registry.configs]
  [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.59.63".tls]
      insecure_skip_verify = true
  [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.59.63".auth]
      username = "admin"
      password = "Harbor12345"

  [plugins."io.containerd.grpc.v1.cri".registry.headers]

    # 改成自己的镜像源
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
    [plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.59.63"]
      endpoint = ["https://192.168.59.63:443"]
    [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
      endpoint = ["crpi-cs92oq2e1ryfz2xe.cn-chengdu.personal.cr.aliyuncs.com"]
  • 重启
$ systemctl enable containerd.service 
$ systemctl restart containerd.service 
$ systemctl status containerd.service 

containerd 命令行工具 ctr

  • 拉取
# 拉取镜像,必须完整格式
$ ctr image pull docker.io/library/nginx:latest
# 查看镜像
$ ctr image ls
  • 导出
$ ctr image pull registry.aliyuncs.com/google_containers/pause:3.2
# 导出
$ ctr image export pause.tar.gz registry.aliyuncs.com/google_containers/pause:3.2
# 可供docker 导入
$ docker load -i pause.tar.gz
  • 导入
$ docker save -o nginx.tar nginx:latest
# 导入
$ ctr image import nginx.tar
  • 跟docker对比 alt text

  • 从 harbor 拉取镜像

$ ctr image pull 192.168.59.63/test/tomcat:v1 --skip-verify --user=admin:Harbor12345
# 查看镜像
$ ctr image ls

docker 4种网络模式

概况

host模式

使用--net=host指定。容器使用主机的IP地址和端口来进行通信,这样可以达到和主 机一样的网络性能和配置,但是也存在安全风险。使用Host网络模式时,容器不会创建自己的网络命名 空间,也不会为容器单独分配IP地址,而是直接使用主机的IP地址和端口,因此容器之间的网络隔离会 被打破,容器可以直接访问主机上所有的端口和服务,也可以被主机上的其他设备访问到。Host网络模 式一般用于需要直接访问主机资源的场景,例如主机上运行的服务需要直接访问容器中的服务。

none模式

使用--net=none指定。None网络模式就是不给容器分配任何网络资源,也就是说, 容器不会创建虚拟网卡、IP地址和路由等网络资源,容器之间无法直接通信,也无法与主机和外部网络 进行通信。使用None网络模式时,容器只能使用本地文件系统和进程空间等资源。None网络模式一 般用于一些特殊场景,例如需要在容器中进行一些本地操作,但又不需要使用网络资源的情况下,或者需 要创建一个完全隔离的容器时。

bridge模式

使用--net=bridge指定,这是·Docker·默认的网络模式。在Docker中,Bridge网络模式就像是一座桥, 它通过一个虚拟网桥来连接多个容器。这个虚拟网桥就像是一座桥梁,把连接到它上面的容器连接起来, 这样它们就可以相互通信了。与外部网络进行通信时,虚拟网桥通过物理网卡连接到主机上的网络。

container模式

Docker网络container模式是指,创建新容器的时候,通过--net container 参数,指定其和已经存在的某个容器共享一个·Network-Namespace。如下图所示,右方黄色新创建的 container,其网卡共享左边容器。因此就不会拥有自已独立的·IP,而是共享左边容器的·IP 172.17.0.2.端口范围等网络资源,两个容器的进程通过·10?网卡设备通信。

alt text

实验

## none 无ip 需要自己配
$ docker run -itd --name none --net=none --privileged=true rockylinux:8.8 bash
## host 与主机共享ip
$ docker run -itd --name host --net=host --privileged=true rockylinux:8.8 bash
## 跟 名称为 none 容器共享ip
$ docker run --name container --net=container:none -it --privileged=true rockylinux:8.8 bash
## 默认就是 -net=bridge 
$ docker run --name bridgee -it --privileged=true rockylinux:8.8 bash

拓展

-privileged=true·#允许开启特权功能,使用这个参数会让Docker容器拥有更高的权限,可以 访问宿主机的所有资源。类比于一个人,如果你是普通的游客,你只能在景区指定的区域内走动,但是如 果你是景区的管理员,那么你就可以随意进出景区的各个区域,进行各种操作。

在Docker中,运行特权容器的场景通常是需要执行一些底层操作,例如管理宿主机上的硬件设备、 加载内核模块、访问系统的底层资源等。因此,如果需要在容器中进行这些底层操作,就需要使用-- privileged=true参数。不过,需要注意的是,这种高权限操作也可能存在安全风险,因此需要仔细考虑 和控制风险。 特权模式通常用于需要访问宿主机底层设备或进行特殊操作的应用场景,例如: 1)需要在容器中执行内核模块加载或卸载操作; 2)需要访问系统中的设备节点,例如/dev和/sys目录下的文件; 3)需要访问系统的硬件资源,例如CPU、内存、网络和存储设备等。

解决文件描述符不足问题

查看当前限制值

# 这将显示当前 shell 的文件描述符软限制。
ulimit -n
# 硬限制
ulimit -Hn
# 查看已经使用 ?
lsof | wc -l
  • 查看整个系统的文件描述符使用情况

 cat /proc/sys/fs/file-nr
6944    0       9223372036854775807
输出三列:

已分配的文件句柄数(in-use) 已分配但未使用的句柄数(unused) 最大可分配句柄数(max)

如果第一列接近第三列,则需要增加系统最大值:

sysctl fs.file-max
如需临时增大:

sudo sysctl -w fs.file-max=2097152
永久生效,编辑 /etc/sysctl.conf 添加:

fs.file-max = 2097152

临时增加限制

ulimit -n 65535

永久增加限制

/etc/security/limits.conf

*               soft    nofile          65536
*               hard    nofile          65536
root            soft    nofile          65536
root            hard    nofile          65536

如果使用 systemd 启动 NFS,还需要设置 systemd 服务级别的限制。 systemctl edit nfs-kernel-server.service

[Service]
LimitNOFILE=65536
保存并退出,然后重新加载 systemd 配置
sudo systemctl daemon-reexec
sudo systemctl daemon-reload

解决问题

inotify 的最大实例数

$ cat /proc/sys/fs/inotify/max_user_instances
128

解决方法: 临时设置更大值:

sudo sysctl -w fs.inotify.max_user_instances=8192
永久设置:编辑 /etc/sysctl.conf 添加:

fs.inotify.max_user_instances = 8192
应用更改:

sudo sysctl -p

还可以检查以下参数,如有必要也适当调大:

cat /proc/sys/fs/inotify/max_user_watches   # 默认 8192
cat /proc/sys/fs/inotify/max_queued_events  # 默认 16384
修改方式同上,在 /etc/sysctl.conf 中添加对应项。

  • 确保 systemd 对服务的限制足够大 即使全局 ulimit 设置正确,systemd 服务也可能有自己的限制。

编辑服务文件:

sudo systemctl edit nfs-kernel-server.service
确保有:

[Service]
LimitNOFILE=1048576
然后重载配置:

sudo systemctl daemon-reload
sudo systemctl restart nfs-kernel-server

helm介绍、组件、安装、和目录结构

为什么需要helm

随着应用增多,需要维护大量的yaml文件,不能根据一套yaml文件来创建多个环境,需要手动修改.

什么是helm和相关组件

  • 概念

    helm是k8s的包管理工具,主要用来管理helm中的各种chart包,可以方便的发现、共享和构建k8s应用。相当于centos中的yum工具,可以将服务相关的所有资源信息整合到一个chart包中,并且可以使用一套资源发布到多个环境中可以将应用程序的所有资源和部署信息组合到单个部署包中.

  • 组件

    Chart: 一个整合后的chart包,包含一个应用所有的k8s声明模板,类似于yum的rpm包 详细理解Chart: helm将打包的应用程序部署到k8s,并将他们构建成Chart,这些chart将所有与配置的应用程序资源以及所有版本都包含在一个易于管理的包中 helm把k8s资源打包到一个chart中,chart被保存到chart仓库,通过chart仓库可用来存储和分享chart helm客户端: helm客户端组件,负责和k8s-apiserver通信 Repository: 用于发布和存储chart包的仓库,类似于yum仓库和docker仓库 Release: 用chart包部署的一个实例,通过chart在k8s中部署的应用都会产生一个唯一的Release,同一chart多次部署就会产生多个Release. 详细理解Release: 将这些yaml部署完成后,会记录部署时的一个版本,维护了一个release版本,通过这个实例,可以创建pod等资源。

v2 和 v3 版本区别

alt text

1、helm3移除了Tiller组件直接使用kubeconfig文件与k8s通信,而helm2中helm客户端是通过Tiller组件和k8s通信的

2、删除release命令变更 helm delete release-name --purge ---->> helm uninstall release-name (helm3)

3、查看charts命令变更 helm inspect release-name ---->> helm show release-name (helm3)

4、拉取chart包命令变更 helm fecth chart-name ---->> helm pull chart-name (helm3)

5、helm3中必须指定release名称,如果需要生成一个随机名称,需要添加--generate-name选项, helm2中如果不指定release名称,可以自动生成一个随机名称 helm install ./mychart --generate-name

helm3的安装和chart的目录结构

helm版本与对应的k8s版本

    Helm 版本 支持的 Kubernetes 版本
    3.8.x       1.23.x - 1.20.x
    3.7.x       1.22.x - 1.19.x
    3.6.x       1.21.x - 1.18.x
    3.5.x       1.20.x - 1.17.x
    3.4.x       1.19.x - 1.16.x
    3.3.x       1.18.x - 1.15.x
    3.2.x       1.18.x - 1.15.x

helm3二进制安装

步骤:
    a) wget https://get.helm.sh/helm-v3.5.2-linux-amd64.tar.gz #下载helm包
    b) tar xf helm-v3.5.2-linux-amd64.tar.gz  && mv linux-amd64/helm /usr/local/bin/
    c) helm version #检查是否安装成功

创建一个Chart包
步骤:
    a) helm create mychart #创建一个chart,指定chart名: mychart
    b) chart的目录结构
    [root@public867 ~]# tree mychart/
    mychart/  #chart包名称
    ├── charts #存放子chart的目录,目录里存放这个chart依赖的所有子chart
    ├── Chart.yaml #保存chart的基本信息,包括名字、描述信息等,这个变量文件可以被templates目录下的文件所引用
    ├── templates #模板文件目录,目录里面存放所有yaml模板文件,包含了所有部署应用的yaml文件
    │   ├── deployment.yaml #创建deployment对象的模板文件
    │   ├── _helpers.tpl #防止模板助手的文件,可以在整个chart中重复使用
    │   ├── hpa.yaml
    │   ├── ingress.yaml
    │   ├── NOTES.txt #存放提示信息的文件,a介绍chart帮助信息,helm install部署后展示给观众,如何使用chart等
    │   ├── serviceaccount.yaml
    │   ├── service.yaml
    │   └── tests #用于测试的文件,测试完部署chart
    │       └── test-connection.yaml
    └── values.yaml #用于渲染模板的文件(变量文件,定义变量的值) 定义templates目录下的yaml文件可能引用到的变量
     #values.yaml用于存储templates目录中模板文件中用到变量的值。

3、Chart.yaml字段说明
apiVersion: v2  #chart版本号
name: mychart #chart名
description: A Helm chart for Kubernetes #对项目的描述信息
type: application # #chart类型(可选) 有两种选择,默认为application,另一种是library 
version: 0.1.0 #chart包的版本(必须)
appVersion: "1.16.0" #apiversion版本

编写一个chart和helm内置对象详解

helm3创建编写一个chart(不引用变量)

1、创建chart
helm create mychart
2、删除掉创建的myachrt中templates目录下的模板,自己新创建一个文件
rm -rf mychart/templates/*
vim mychart/templates/configmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
         name: mychart-test-configmap
    data:
        myvalue: "hello-world"
3、创建一个release实例
helm install mytestconfigmap ./mychart/
4、列出创建的release实例
helm list
NAME            NAMESPACE   REVISION    UPDATED          STATUS     CHART    APP VERSION
mytestconfigmap default     1           2023-05-02     deployed   mychart-0.1.0 1.16.0
5、查看创建后的相关信息和验证k8s是否已经创建了该configmap
helm get manifest mytestconfigmap
    ---
    # Source: mychart/templates/configmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
         name: mychart-test-configmap
    data:
        myvalue: "hello-world"
kubectl get cm
NAME                     DATA   AGE
mychart-test-configmap   1      14m

helm3创建编写一个简单的chart(引用变量)

1、vim configmap-variable.yaml #在templates目录下创建该文件
apiVersion: v1
kind: ConfigMap
metadata:
     name: {{ .Release.Name }}-configmap #最前面的,从作用域最顶层命名空间开始,即:在顶层命名空间中开始查找Release对象,在查找Name对象 实际就是通过内置对象获取内置对象的变量值(release的名称)作为拼接成configmap的名字
data: 
    myvalue: {{ .Values.MY_VALUE }} #调用values.yaml文件中定义的变量MY_VALUE获取变量值
2、在mychart/values.yaml文件中定义MY_VALUE变量
vim values.yaml
MY_VALUE: "HELLO WORLD"
3、创建一个实例
helm install myvariablecfg ../mychart/
4、查看release实例
helm list
NAME          NAMESPACE REVISION     UPDATED     STATUS        CHART         APP VERSION
myvariablecfg   default     1         2023-05-02    deployed    mychart-0.1.0   1.16.0  
5、通过k8s查看是否已经创建
kubectl get configmap
myvariablecfg-configmap   1      20s #其中name这一个字段就是在myvariable.yaml文件中进行拼接后产生的名字
  • 总结: 通过使用变量的方法,后期更改方便,只需要更改变量文件即可

helm测试渲染命令

1、helm提供了一个用来渲染模板的命令,该命令可以将模板内容渲染出来,但是不会进行任何安装的操作,可以用该命令来测试模板渲染的内容是否正确,相当于一个测试命令
命令:
    helm install release实例名称 chart目录 --debug --dry-run

helm3的内置对象详解

常用的内置对象
    Release对象
    Values对象
    Chart对象
    Capabilities对象
    Template对象

内置对象详解

1、Release对象详解  描述了版本发布自身的一些信息
对象名称                    描述
.Release.Name               release的名称
.Release.Namespace          release的命名空间
.Release.IsUpgrade          如果当前操作是升级或回滚的话,该值为true
.Release.IsInstall          如果当前操作是安装的话,该值为true
.Release.Revision           获取此次修订的版本号,初次安装为1,每次升级或回滚会自增
.Release.Service            获取渲染当前模板的服务名称,一般都是Helm
2、Values对象详解  描述的是value.yaml文件中的内容,默认为空.使用value对象可以获取到该文件中已定义的任何变量数值
value键值对                获取方法
name1:test1                 .Values.name1
info:
  name2:test2               .Values.info.name2
3、Chart对象   用于获取Chart.yaml文件中内容
对象名称                    描述
.Chart.Name                 获取chart内容
.Chart.Version              获取chart版本
4、capabilities对象   提供了关于k8s集群的信息
对象名称                    描述
.Capabilities.APIVersion    返回k8s集群 API版本信息集合
.Capabilities.APIVersion.Has $version   用于检测指定的版本或资源在k8s集群中是否可用
.Capabilities.KubeVersion    获取k8s的版本号
.Capabilities.KubeVersion.Version  获取k8s的版本号
.Capabilities.KubeVersion.Major   获取k8s主版本号
.Capabilities.KubeVersion.Minor   获取k8s小版本号
5、Template对象  获取当前模板的信息
对象名称                    描述
.Template.Name              用于获取当前模板的名称和路径 (例如: mychart/templates/mycfg.yaml)
.Template.BasePath          用于获取当前模板的路径 (例如: mychart/templates)

helm3常用命令和部署应用实战案例

helm3常用命令

1、helm version 查看helm客户端版本
2、helm repo add 仓库别名 仓库地址  添加chart仓库
3、helm repo index 仓库别名 仓库地址  索引chart仓库
4、helm repo list 仓库别名 仓库地址 列出chart仓库
5、helm repo remove 仓库别名 仓库地址 移除chart仓库
6、helm repo update 仓库别名 仓库地址  更新chart仓库
7、helm search repo/hub xx  根据关键字搜索chart包
8、helm  show all/chart/readme/values chart包名  查看chart包的基本信息和详细信息
9、helm pull test-repo/tomcat --version 0.4.3 -untar 从远程仓库中下载拉取chart包并解压到本地。--untar是解压,如果不添加则为不解压chart包
10、helm create chart包名  创建chart包
11、helm install 实例名 chart包名  安装一个relase实例
12、helm install --values templates/my-values.yaml <release-name> <chart-name> 指定要执行的特定 YAML 文件
13、helm list 列出实例名
14、helm upgrade  <release-name> <chart-name> 更新实例
    helm upgrade  <release-name> <chart-name> -f path文件路径 更新实例
15、helm rollback <release-name> 版本号  回滚到指定版本
16、helm uninstall <release-name>   卸载实例
17、helm history <release-name>  获取release实例历史
18、helm package chart路径  将chart目录打包成chart存档文件中,即打成tgz的压缩包
19、helm get manifest <release-name>  获取指定 release 的 K8s 渲染的 YAML 文件
20、helm get all <release-name> 查看实例所有信息
21、helm get notes <release-name> 获取 release 的备注信息
22、helm get values <release-name> 获取指定 release 的配置值信息
23、helm get hooks <release-name>  获取指定 release 中所有的钩子(hooks)
24、helm status <release-name> 显示该实例的状态,显示已命名版本的状态
27、helm upgrade <release-name> chart名 --set imageTag=1.19 指定实例和chart名进行相关set设置的升级
28、helm upgrade <release-name> chart名 -f ../mychart/values.yaml 指定实例和chart名和values.yaml升级

helm3部署应用的实战案例(发布、升级、回滚、卸载)

1、创建chart
helm create nginx-chart
2、清空默认的templates的内容,自己创建模板文件
vim nginx-chart/templates/nginx-deploy-svc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.Name }}
  namespace: {{ .Values.Namespace }}
spec:
  replicas: {{ .Values.replicas }}
  selector:
    matchLabels:
      app: {{ .Values.pod_label }}
  template:
    metadata:
      labels:
        app: {{ .Values.pod_label }}
    spec:
      containers:
      - image: {{ .Values.image }}:{{ .Values.imageTag }}
        name: {{ .Values.container_name }}
        ports:
        - containerPort: {{ .Values.containerport }}
---
apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.Name }}
  namespace: {{ .Values.Namespace }}
spec:
  type: NodePort
  ports:
    - port: {{ .Values.port }}
      targetPort: {{ .Values.targetport }}
      nodePort: {{ .Values.nodeport }}
      protocol: TCP
  selector:
    app: {{ .Values.pod_label }}
3、在nginx-chart/values.yaml文件中添加上述文件中变量对应的变量值
    deployment_name: nginx-deployment
    replicas: 2
    pod_label: nginx-pod-label
    image: nginx
    imageTag: "1.19"
    container_name: nginx-container
    service_name: nginx-service
    Namespace: cityos
    port: 80
    targetport: 80
    containerport: 80
    nodeport: 8080
    Name: nginx
4、发布这个实例
helm install nginx-1.19 nginx-chart
5、验证
helm list   kubectl get pod,svc -n cityos
6、更新helm  修改values.yaml文件中的镜像版本至1.20
helm upgrade nginx-1.19 nginx-chart -f /export/nginx-chart/values.yaml
helm upgrade nginx-1.19 nginx-chart --set imageTag=1.20
7、再次验证成功
8、回滚至1.19版本
helm rollback nginx-1.19 不添加版本号就回滚到上一个版本
9、验证成功
10、卸载chart
helm unisntall nginx-1.19

helm3内置函数详解

helm3的内置函数

0、函数的使用格式
    格式1: 函数名 arg1 arg2...
    格式2: arg1 | 函数名 更偏向使用这种格式
1、quote或squote函数
    quote: 调用的变量值添加一个双引号
    squote: 调用的变量值添加一个单引号
示例:
templates/quotefunc.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-quote
      namespace: {{ .Release.Namespace }}
    data:  #value{1,2}格式1写法  value{3,4}格式2写法
      value1: {{ quote .Values.name }} #调用的变量值添加一个双引号
      value2: {{ squote .Values.name }} #调用的变量值添加一个单引号
      value3: {{ .Values.name | quote }}
      value4: {{ .Values.name | squote }}
values.yaml
    name: test
只进行渲染不进行实际执行 看一下结果
helm install quotefunc chartfunc --debug --dry-run
部分结果如下:
    Source: chartfunc/templates/quotefunc.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: quotefunc-quote
      namespace: default
    data:
      value1: "test" #调用的变量值添加一个双引号
      value2: 'test' #调用的变量值添加一个单引号
2、upper和lower函数
    upper: 将字符串转换成大写
    lower: 将字符串转换成小写
示例:
templates/upperlowerfunc.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}
      namespace: {{ .Release.Namespace }}
    data:  #value{1,2}格式1写法  value{3,4}格式2写法
      value3: {{ .Values.name | quote | upper }} #转成大写并添加双引号
      value4: {{ .Values.name | squote | lower }} #转成小写并添加单引号
values.yaml
    name: test
只进行渲染不进行实际执行 看一下结果
helm install upperlowerfunc chartfunc --debug --dry-run
部分结果如下:
    Source: chartfunc/templates/upperlowerfunc.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: upperlowerfunc
      namespace: default
    data:
      value1: "TEST" #调用的变量值添加一个双引号
      value2: 'test' #调用的变量值添加一个单引号 
3、repeat函数
    repeat: 将指定字符串重复输出指定的次数,repeat函数可以带有一个参数,用于设置重复多少次
示例:
templates/repeatfunc.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}
      namespace: {{ .Release.Namespace }}
    data:
      value1: {{ .Values.name | repeat 2 | quote }} #重复输出2次并将结果添加双引号并转换成大写
values.yaml
    name: test
输出结果
    # Source: chartfunc/templates/repeatfunc.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: repeatfunc
      namespace: default
    data:
      value1: "testtest" #重复输出2次并将结果添加双引号并转换成大写
4、default函数
    default: 使用default函数指定一个默认值,当引入的值不存在时,可以使用这个默认值
示例:
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}
      namespace: {{ .Release.Namespace }}
    data:
      value1: {{ .Values.location | default "BEIJING" | quote }} #调用变量值,引用的变量location不存在时使用定义的默认值且添加一个双引号
values.yaml中未定义location这个变量
执行结果:
    # Source: chartfunc/templates/defaultfunc.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: repeatfunc
      namespace: default
    data:
      value1: "BEIJING" #调用变量值,引用的变量location不存在时使用定义的默认值且添加一个双引号
5、lookup函数
    lookup: 用于在当前k8s集群中获取一些资源的信息,类似于kubectl get ...
    格式: lookup "apiVersion" "kind" "namespace" "name"
    常用格式和kubectl命令相互对应关系:
    kubectl命令                           lookup函数
    kubectl get pod -n cityos          lookup "v1" "Pod" "cityos"
    kubectl get ns                        lookup "v1" "Namespace" "" ""
示例:
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}
      namespace: {{ .Release.Namespace }}
    data:
      value1: {{ lookup "v1" "Namespace" "" "" | quote }}
查看执行结果
helm get manifest lookfunc
 value1: "map[apiVersion:v1 items:[map[apiVersion:v1 kind:Namespace metadata:map[creationTimestamp:2022-10-31T10:07:51Z managedFields:[map[apiVersion:v1 fieldsType:FieldsV1 fieldsV1:map[f:status:map[f:phase:map[]]] manager:kubectl operation:Update time:2022-10-31T10:07:51Z]] name:cityos resourceVersion:1199866 selfLink:/api/v1/namespaces/cityos uid:a492f100-c025-48a1-89b"

helm3的逻辑和流程控制函数

eq: 参数是否相等  等于为true 反之为false
    {{ eq 参数1 参数2 }}
ne: 参数是否不相等  不等于为true  反之为false
    {{ ne 参数1 参数2 }}
lt: 第一个参数是否小于第二个参数  小于为true 反之false
    {{ lt 参数1 参数2 }}
le: 第一个参数是否小于等于第二个参数  小于等于为true  反之false
    {{ 1e 参数1 参数2 }}
gt: 第一个是否大于第二个参数  大于为true 反之false
    {{ gt 参数1 参数2 }}
ge: 第一个参数是否大于等于第二个参数  大于等于为true  反之false
    {{ ge 参数1 参数2 }}
and: 返回两个参数的逻辑与结果(布尔值) 两参数为真 结果为true 反之为false
    {{ and .Values.name1 .Values.name2 | quote }}
or: 判断两个参数的逻辑或结果  其中有一个参数为真,结果为true 反之为false
    {{ or .Values.name1 .Values.name2 | quote }}
not: 对参数的布尔值取反,如果参数是正常参数(非空),正常为true 反之是false
    {{ not .Values.name1 | quote }}
default: 设置一个默认值 在参数的值为空的情况下,则使用默认值
    {{ .Values.location | default "BEIJING" | quote }}
empty: 用于判断给定的值是否为空,如果为空返回为true,反之是false
    {{ 值 | empty }}
coalesce: 用于扫描一个给定的列表,并返回第一个非空的值
    {{ coalesce 0 1 2 }}
ternary: 接收两个参数和一个test值,如果test的布尔值为true,则返回第一个参数的值,反之则返回第二个参数的值
    {{ ternary 参数1 参数2 true }}

helm3的字符串函数

1、trim、trimAll、trimPrefix、trimSuffix函数
含义:
    trim: 用来去除字符串两边的空格,示例: trim " hello "
    trimAll: 移除字符串中指定的字符 示例: trimAll "$" "$5.00"
    trimPrefix和trimSuffix: 移除字符串中指定的前缀和后缀 示例: trimPrefix "-" "-hello"
示例:
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-configmap
      namespace: {{ .Release.Namespace }}
    data:
      data1: {{ trim " test " }}
      data2: {{ trimAll "%" "%test" }}
      data3: {{ trimPrefix "-" "-test" }}
      data4: {{ trimSuffix "+" "test+" }}

2、lower、upper、title、untitle函数
含义:
    lower: 将所有字母转换成小写
    upper: 将所有字母转换成大写
    title: 将首字母转换成大写
    untitle: 将大写的首字母转换成小写
示例:
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}
      namespace: {{ .Release.Namespace }}
    data:   
      value3: {{ lower "HELLO" }}
      value4: {{ upper "hello" }}
      value5: {{ title "hello" }}
      value6: {{ untitle "Hello" }}

3、snakecase、camelcase、kebabcase函数
含义:
    snakecase: 将驼峰写法转换为下划线命名写法
    camelcase: 将下划线命名写法转换为驼峰写法
    kebabcase: 将驼峰写法转换为中横线写法
示例:
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: trimcfg-test
      namespace: default
    data:
      data1: user_name
      data2: UserName
      data3: user-name

4、substr函数 切割字符串(指定切割起、始位置),并返回切割后的字串
三个参数:
    start(int): 起始位置
    end: 结束位置
    string: 需要切割的字符串
示例:
     apiVersion: v1
    kind: ConfigMap
    metadata:
      name: trimcfg-test
      namespace: default
    data:
      data3: {{ substr 3 5 "message" }} #sa

5、trunc函数  截断字符串 使用正整数或负整数来分别表示从左向右截取的个数和从右向左截取的个数
示例:
    trunc 5 "Hello test" #Hello
    trunc -5 "Hello world" #world

6、abbrev函数  使用省略号(...)切割字符串,保留指定长度。其中省略号也是计算在长度之内的
示例:
    abbrev 5 "Hello world" #"He..."

7、randAlphaNum、randAlpha、randNumeric、randAscii 用于生成加密的随机字符串,指定生成的字符串长度
含义:
    randAlphaNum: 使用0-9a-zA-Z生成随机字符串
    randAlpha: 使用a-zA-Z生成随机字符串
    randNumeric: 使用0-9 生成随机字符串
    randAscii: 使用所有的可使用的ASCII字符,生成随机字符串
示例:
    randAscii 10 生成长度为10的字符串,其余三个也一样的用法

8、hasPrefix、hasSuffix函数 测试一个字符串是否是指定字符串的前后缀的,返回布尔值
示例:
    hasPrefix "He" "Hello" #true
    hasSuffix "He" "Hello" #false

9、nospace 去除字符串中所有的空格
示例:
    nospace " Test"

10、initials 截取指定字符串的每个单词的首字母并拼接在一起
示例:
    initials "Peplo Peace" #PP

11、wrapWith函数 在文档指定的列数添加内容 例如添加内容"\t"
示例:
    wrapWith 5 "\t" "Helloworld" #Hello  world

12、cat函数 用于将多个字符串合并成一个字符串并使用空格分开
示例:
    cat "Hello" "test" #"Hello test"

13、replace  用于执行简单的字符串替换 需要传递三个参数
第一个参数: 待替换的字符串
第二个参数: 将要替换的字符串
第三个参数: 源字符串
示例:
    "I AM TEST" |replcae "" "-" #"I-AM-TEST" 

14、shuffle 对字符串中的字符进行重新排序
示例:
    shuffle "Hello" #Helol

15、indent、nindent 用于以指定长度来缩进指定字符串的所在行,其中nindent函数可以在缩进时在字符串开头添加新行
示例:
    indent 4 "test"
    nindent 5 "test"
区别:
    nindent函数可以在缩进时在字符串开头添加新行,也就是在该行字符串上方会添加一行新的空行

16、plural函数 判断字符串的长度,并且根据长度返回不同的值,例如字符串的长度为1会返回plural函数的第一个参数,若不是1,则返回第二个参数
示例:
    len "a" |plural "one" "many" #one
    len "ab" |plural "one" "many" #many

helm3的类型转换和正则表达式函数

类型转换函数:带*号的表示常用的
    (*)atoi函数: 将字符串转换为整型
    (*)float64函数: 将字符串转换为flota64类型
    (*)int函数: 转换为int类型
    (*)toString函数: 转换为字符串
    int64函数
    toDecimal函数: 将unix八进制转换成int64
    toStings: 将列表、切片或数组转换成字符串列表
    toJson: 将列表、切片或数组、字典转换成json
示例:
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-test
    data:
      data1: {{ "16" | kindOf }} #转换前查看是啥类型,字符串
      data2: {{ atoi "16" | kindOf }} #将字符串类型转换为整型,kindOf函数可以判断传入的这个参数的类型是什么
      data3: {{ toString "16" | kindOf }}   
正则表达式函数:
    regexFind和mustRegexFind函数
    regexFindAll和mustRegexFindAll函数
    regexMatch和mustRegexMatch函数:
        根据指定的正则来匹配字符串,匹配成功返回true
        如果表达式有问题,regexMatch会直接抛出错误,mustRegexMatch会向模板引擎返回错误
        示例:
            apiVersion: v1
            kind: ConfigMap
            metadata:
              name: {{ .Release.Name }}-rege
            data:
              data1: {{ regexMatch "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" "test@xxx.com" }}
              data2: {{ mustRegexMatch "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" "test@xxx.com" }}
    regexReplaceAll和mustRegexReplaceAll函数
    regexReplaceAllLiteral和mustRegexReplaceAllLiteral函数
    regexSplit和mustRegexSplit函数:
        指定一个分隔符,将字符串进行切割,并返回切割后的字符串切片

helm3的加密函数和编码解码函数

加密函数:
    sha1sum、sha256sum、adler32sum、htpasswd、encryptAES、decryptAES
编码、解码函数:
    Base64、Base32

helm3的日期函数

常用的日期函数:
    now函数: 用于返回当前日期时间
    date函数: 将日期信息格式化 示例: now | date "2006-01-02"
    dataInZone函数: 与date用法一致,但可以指定时区返回时间 示例: dataInZone "2006-01-02"(now)"UTC"
    duration函数: 将给定的秒数转换为几分几秒的形式
    durationRound函数: 将给定的日期进行取整,保留最大的单位 例如:2h10m,结果就是2h
    unixEpoch函数: 返回给定时间的时间戳格式
    dataModify、mustDateModify函数:
        将一个给定的日志修改一定的时间并返回修改后的时间
        区别:
            如果修改格式错误,前者会返回日期未定义,后者返回错误
    toDate、mustToDate函数:
        将指定的字符串转换成日期,第一个参数需要指明要转成的日期格式,第二个参数需要传递转换的字符串
        区别:
            如果字符串无法转换,前缀返回0值,后者抛出错误

helm3的字典函数

dict字典函数、get、set、unset函数
含义:
    dict: 用于存储key/value键值对,key必须是字符串类型,value可以是任意类型
        示例:
            $myDict=dict "name1" "value1" "name2" "value2"
    get: 获取定义字典的值
        示例:
            get $myDict "name1"
    set: 向已有的字典中添加新的键值对,也可以修改原来键值对的值
        示例:
            set $myDict "name3" "value3"
            set $myDict "name2" "newvalue2"
    unset: 删除字典中的key
        示例:
            unset $myDict "name3"
示例:
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-dict
    data:
      {{- $myDict := dict "name1" "value1" "name2" "value2" }}
      data1: {{ $myDict }} #map[name1:value1 name2:value2] 
      data2: {{ get $myDict "name1" }} #value1
      data3: {{ set $myDict "name2" "newvalue3" }} #map[name1:value1 name2:newvalue3]
      data4: {{ unset $myDict "name1" }} #map[name2:newvalue3]
keys函数
    用于获取一个或多个字典中所有的key并返回一个列表.字典是无序的,列表中如果包含多个相同的key,可以使用sortAlha函数对列表进行排序,在使用uniq函数去重
    在指定多个字典且字典存在相同key时,这些key都会保存到列表中

helm3的列表函数

1、常用的列表函数及含义
    (1)、list函数、first函数、rest函数、last函数、initial函数
        含义:
            list函数: 用于生成一个列表,传入的参数将会作为列表的值.$myList:=list 1 2 3 "one"
            first函数: 用于获取列表的第一项 first $myList
            rest函数: 用于获取列表中除第一项以外的所有内容 rest $myList
            last函数: 用于获取列表的最后一项 last $myList
            initial函数: 用于获取列表中除最后一项以外的内容 与rest恰好相反 initial $myList

    (2)、append函数、prepend函数、concat函数、reverse函数、uniq函数
        含义:
            append函数: 在已有的列表中追加一项,并返回一个新的列表。原列表内容保持不变。$newList=append $列表变量  要追加的内容
            prepend函数: 在列表中最前面加入一个新值,并返回一个新的列表,原列表内容不变. $newList=append $列表变量  最前面加的值
            concat函数: 用于将任意数量的列表合并成一个新的列表,原列表内容不变。 concat $myList (list 100 101 102) (list a b c)
            reverse函数: 用于反转一个列表,并返回一个新的列表 $reverse $myList
            uniq函数: 用于去除一个列表中的重复项 并返回一个新的列表。 list 1 1 2 3 2 | uniq

    (3)、without函数、has函数、compact函数
        含义:
            without函数: 用于过滤掉列表中的指定值,并返回包含剩下值的列表。 without (list 1 2 3) 3(过滤掉3)
            has函数: 用于判断一个值是否包含在列表中,包含返回true 反之为false has "hello" (list 1 2 3) |quote (判断hello是否在列表中)
            compact函数: 用于删除一个列表中的空值,并返回一个新的列表 compact (list 1 2 3 "") |quote (注意:空值包括: 整型 0、字符串 ""、列表 []、字典 {}、布尔 false、以及所有的nil或null)

    (4)、slice函数、until函数、utilStep函数、seq函数
        含义:
            slice函数: 用于对列表进行切片  slice list [n][m] 相当于从索引为n开始切片至索引m(左闭右开区间)
            until函数: 用于构建一个指定整数范围内的列表 until 5
            untilStep函数: 与until类似,不过可以定义整数的开始和步长 untilStep 3 9 2 输出3-9之间的数字,且步长值为2
            seq函数: 用于生成指定范围内的整数。最多可以传递三个参数:
                单个参数(结束位置)-会生成所有从1到包含end的整数
                多个参数(开始、结束)-会生成所有包含start和end的整数,递增或递减
                多个参数(开始、步长、结束)-会生成所有包含start和end 按step递增或递减的整数
                示例:
                    seq 5 --> 1 2 3 4 5
                    seq -3 -->1 0 -1 -2 -3
                    seq 0 2 10 --> 0 2 4 6  10

helm3的网络函数、文件路径函数、类型检查函数

网络函数:
    getHostByName
    含义:
        用于接收一个域名并返回该域名解析后的IP地址
    示例:
        getHostByname "www.baidu.com" |quote

文件路径函数:
    base函数
    含义:
        用于返回指定路径的最后一级目录
    示例:
        base "/tmp/a/a.txt" #a.txt
    dir函数
    含义:
        用于返回指定路径的上一级路径
    示例:
        dir "/tmp/a/a.txt" #/tmp/a
    ext函数
    含义:
        用于返回文件的拓展名 
    示例:
        ext "a.txt" #.txt
    isAbs函数
    含义:
        用于判断文件路径是否是绝对路径,绝对路径返回true,否则返回false
    示例:
        isAbs "/tmp/a" #true

类型检查函数和对比函数:
    kindOf函数
    含义:
        用于返回对象的类型
    示例:
        kindOf (list 1 2 3)
    kindIs函数
    含义:
        用于检测某个对象是否是指定的类型,返回布尔值,第一个参数需要指定类型,第二个参数需要指定检查的数据对象
    示例:
        kindIs "string" "hello"
    deepEqual函数
    含义:
        用于判断两个对象的值是否完全一致,返回布尔值
    示例:
        deepEqual (list 1 2 3) (list 2 3 4)

流程控制结构语句

流程控制语句if/else

if/else语句中的条件在模板中称为管道,基本结构如下:
    {{- if PIPELINE }}
      #do something
    {{- else if OTHER }}
     #do something
    {{- else }}
      #default case
    {{- end }}
如果管道中存在空值时,管道的返回值会设置为false
示例:
    设置values.yaml文件
    Person:
      name: test
      work: IT
      age: 16
      sex: man
    ingress:
      enabled: true
    创建模板
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-test
      namespace: {{ .Release.Namespace }}
    data:
      name: {{ .Values.Person.name | default "test_name" |quote }}
      sex: {{ .Values.Person.sex | upper |quote }}

      {{- if .Values.ingress.enabled }}
      ingress: "配置ingress"
      {{- else }}
      ingress: "未配置ingress"
      {{- end }} 

      {{- if eq .Values.Person.work "IT" }}
      WORK: {{ .Values.Person.work | quote }}
      {{- else }}
      WORK: "other work"
      {{- end }}

with语句

with语句主要用来控制变量的范围,也就是修改查找变量的作用域
示例:
    data:
      #正常调用values.yaml文件,引用好多变量对象时,会重复写很多相同的引用
      Name: {{ .Values.people.info.name  }}
      Age: {{ .Values.people.info.age  }}
      #通过with语句。效果和上面一样,引用很多重复的变量对象时,可以with语句将重复的路径作用域设置过来
      {{- with .Values.people  }}
      Name: {{ .info.name }}
      Age: {{ .info.age }}
      {{- end }}

range语句

语法格式:
    {{- range 要遍历的对象 }}
    #do something
    {{- end }}
示例:
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-test
      namespace: {{ .Release.Namespace }}
    data:
      address: |-
        {{- range .Values.addess }}
         - {{ . | title }} #用.点作为输出并将首字母大写
        {{- end }}
        {{- range tuple "bj" "sh" "guang" }}
        - {{ . | title }}
        {{- end }}

helm3中变量详解

helm3中变量在作用域、列表、元组、字典中的使用

变量通常是搭配with语句和range语句使用,能有效的简化代码
变量的定义格式如下:
    $name:=value
(1) 使用变量解决对象作用域问题
    因为with语句里不能调用夫级别的变量,所以如果需要调用父级别的变量,需要声明一个变量名,将父级别的变量值赋值给声明的变量。
    在前面关于helm流控制结构的文中提过使用with更改当前作用域的用法,当时存在一个问题是在with语句中,无法使用父作用域中的对象,需要使用$符号或者将语句移到{{- end }}的外面才可以
    示例:
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: {{ .Release.Name }}-test
          namespace: {{ .Release.Namespace }}
        data:
           {{- $releaseName:=.Release.Name }} #定义一个变量
           {{- with .Values.people.info }}
           name: {{ .name }}
           age: {{ .age }}
           #release1: {{ .Release.Name }} 在with语句内,因为变量作用域,不能调用父级别的变量,且会报错
           release2: {{ $releaseName }} #通过变量解决调用父级别的变量
           {{- end }}
           release3: {{ .Release.Nmae }} #在with语句外,可以调用父级别的变量

(2)  变量在列表或元组中的使用
    示例:
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: {{ .Release.Name }}-test
          namespace: {{ .Release.Namespace }}
        data:
          address: |-
            {{- range $index,$add:=.Values.address }}
            {{ $index }}: {{ $add }}
            {{- end }}
     结果:
        address: |-
          0: xx
          1: xx

(3) 变量在字典中的使用
    示例:
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: {{ .Release.Name }}-test
          namespace: {{ .Release.Namespace }}
        data:
           address: |-
            {{- range $key,$value:=.Values.address }}
            {{ $key }}: {{ $value }}
            {{- end }} 

helm3中子模块的定义和使用

helm3中define定义子模版、template和include调用

1、定义子模板的两个位置
    (1)、主模板中
    (2)、_helpers.tpl文件内
定义子模版,可以在主模板中定义,也可以在其他文件中定义(_helpers.tpl文件内,是专门提供的定义子模版的文件),实际使用中,这些子模版的内容应当放在单独的文件中,通常是_helpers.tpl文件内
2、子模版的定义和调用
    定义子模版: 通过define定义
    调用子模版: 通过template或include调用(推荐include)
        区别:
            template不能被其他函数修饰,include是可以的
示例: 使用define在柱模版中定义子模版,使用template调用子模版
vim /root/mychart/templates/configmap.yaml
{{- define "mychart.labels" }} #定义子模版
labels:
  author: test
  data: {{ now | htmlDate }}
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-test
  {{- template "mychart.labels" }} #调用上面的子模版
data:
  data1: "hello1"
示例: 使用define在_helpers.tpl文件中定义子模版,使用template调用子模版
#定义子模版
vim /root/mychart/templates/_helpers.tpl
{{- define "mychart.labels" }}
labels:
  author: test
  data: {{ now | htmlDate }}
{{- end }}
#调用上面的子模版
vim /root/mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-test
  {{- template "mychart.labels" }}
data:
  data1: "hello1"
示例: 向子模版中传入对象,使用template调用子模版
    注意事项:
        在子模版中,如果引用了对象,那么渲染的时候子模版中是无法获取到对象信息的,所以如果直接调用子模版会报错
        解决这个问题需要在引用子模版的同时将对象的位置传递进去即可
        主模板引用子模版时候需要指定对象的位置信息,这个点"."表示在顶层作用域中寻找子模版中指定对象
#定义变量赋值
vim /root/mychart/values.yaml
Person:
  info:
    name: test
    work: IT
    age: 16
    sex: man
ingress:
  enabled: true

#定义子模版vim /root/mychart/templates/_helpers.tpl  
{{- define "mychart.labels" }}
labels:
  author: {{ .Values.Person.info.name }} #
  work: {{ .Values.Person.info.work }}
{{- end }}

#调用上面的子模版vim /root/mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:  
  name: {{ .Release.Name }}-test  
  {{- template "mychart.labels" . }} #注意此处的点"."
data:  
  data1: "hello1"
示例: 向子模版中传入对象,使用include进行调用子模版
#定义变量赋值
vim /root/mychart/values.yaml
Person:
  info:
    name: test
    work: IT
    age: 16
    sex: man
ingress:
  enabled: true
#定义子模版vim /root/mychart/templates/_helpers.tpl  
{{- define "mychart.labels" }}
labels:
  author: {{ .Values.Person.info.name }}
  work: {{ .Values.Person.info.work }}
{{- end }}
#调用上面的子模版vim /root/mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:  
  name: {{ .Release.Name }}-test  
  labels:
  {{- template "mychart.labels" . |toString |ident 4 }} #注意此处的点"."
data:  
  {{- template "mychart.labels" . |toString |ident 2 }} #注意此处的点"."

helm3中获取其他文件的内容或文件名

helm中获取其他文件的内容或文件名

1、使用Get方法获取其他文件的内容
2、使用Glob方法获取文件名和内容
3、使用lines方法循环遍历并逐行读取文件中的内容
示例方法一、
    使用Get方法获取其他文件的内容
helm提供了.Files对象用于访问文件,其中包含了一些方法用于处理文件中的内容
vim /root/mychart/config/test.txt #自定义一个目录,该目录下自定义一个其他文件
  test message
 vim /root/mychart/templates/configmap.yaml
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: {{ .Release.Name }}
 data:
   token1:
 {{.Files.Get "config/test.txt" |b64enc| indent 4 }} #获取文件内容并使用b64enc进行编码缩进
   token2: {{.Files.Get "config/test.txt" |quote }}
示例方法二、
    使用Glob方法获取文件名和内容
1、创建自定义目录并在目录中创建文件
2、vim /root/mychart/templates/configmap.yaml
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: {{ .Release.Name }}
 data:
 {{- range $path,$_:= .Files.Glob "**.conf" }} # **代表匹配任意字符 $_是一个占位符,必须要存在,获取一个空
   path: {{ $path }}
 {{- end }}
 {{(.Files.Glob "config1/*").AsConfig | indent 2 }} #使用AsConfig方法显示文件内容(configmap形式) 
 {{(.Files.Glob "config2/*").AsSecrets | indent 2 }} #使用AsSecrets方法显示文件内容(secret形式显示)
示例方法三、
    使用lines方法循环遍历并逐行读取文件中的内容
1、创建自定义目录并在目录中创建文件
2、vim /root/mychart/templates/configmap.yaml
apiVersion: v1
 kind: ConfigMap
 metadata:
   name: {{ .Release.Name }}
 data:
 {{- range $index,$line:= .Files.Lines "config/test.txt" }} #使用.Files对象中的Lindes方法循环遍历后面文件中的每行内容并赋值给line变量,将每行索引赋值给index变量 Lines方法通常和range一起使用,可以变量文件中的每一行并输出
   {{- if $line }} #Lines方法遍历行时,最后会输出一个空行,此处使用if语句来判断当前行的内容是否为空,如果不为空,则输出
   {{ $index}}: {{ $line | quote }}
   {{- end }}
 {{- end }}  

原文链接:https://blog.csdn.net/weixin_50902636/article/details/144419925

python打包相关

setup.py

早期 python 的测试和打包方式, setup.py 如下

from setuptools import setup

setup(
    name='HelloDolly',
    version='1.0.1',
    packages=['hellodolly'],
    url='https://github.com/fmaida/hello-dolly-mkdocs-plugin',
    license='MIT',
    author='Francesco Maida',
    author_email='fran@cesco.it',
    description='Hello Dolly is a very simple mkdocs plugin.',
    install_requires=['mkdocs'],

    # The following rows are important to register your plugin.
    # The format is "(plugin name) = (plugin folder):(class name)"
    # Without them, mkdocs will not be able to recognize it.
    entry_points={
        'mkdocs.plugins': [
            'hello-dolly = hellodolly:HelloDolly',
        ]
    },
)

现如今对应命令被弃用

Deprecated Recommendation
python setup.py install python -m pip install .
python setup.py develop python -m pip install --editable . 或 python -m pip install -e .
python setup.py sdist python -m build
python setup.py bdist_wheel python -m build
  • python setup.py install:

作用:安装包到系统的 Python 环境中。 特点:会将包复制到 Python 的安装目录下的 site-packages 文件夹中。如果目标目录没有写权限,则可能需要使用 sudo 或者以管理员身份运行。

  • python setup.py develop:

作用:将当前目录作为包安装,即在不复制文件的情况下让 Python 解释器可以找到并使用这个包。 特点:适合开发过程中频繁修改代码的情况,因为不需要重新安装包即可看到修改后的效果。这通过创建一个指向源代码的链接来实现。

  • python setup.py sdist:

作用:生成源码发布包(通常为 .tar.gz 或 .zip 格式)。 特点:打包后可以在其他机器上使用 pip install 包名 来安装此包。

  • python setup.py bdist_wheel:

作用:生成 wheel 发布包(.whl 文件)。 特点:与系统相关的二进制包,安装速度比源码包快,可以直接被 Python 解析和使用,无需编译步骤。

  • python -m build

作用: 生成源码包和 wheel 包。

需要先安装 build 模块 :pip install build

注意 python setup.py develop 或 python -m pip install -e . 测试完后可以用 pip uninstall <> 来解除连接

pypi 发布包

  1. 创建账号
  2. 申请api token
  3. 配置 pypi
    poetry config pypi-token.pypi <token>
    
  4. 编译
python -m build
  1. 发布
    poetry publish
    

windows 端口转发

windows 端口转发

  • netsh interface portproxy add v4tov4 listenport=源端口 listenaddress=本地IP connectport=目标端口 connectaddress=目标IP protocol=tcp
# 添加转发
netsh interface portproxy add v4tov4 listenport=15006 connectaddress=127.0.0.1 connectport=5006
# 查看转发
netsh interface portproxy show all
# 删除转发
netsh interface portproxy delete v4tov4 listenport=15006