简介

ECharts是一款基于JavaScript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。ECharts最初由百度团队开源,并于2018年初捐赠给Apache基金会,成为ASF孵化级项目。

在 Golang 这门语言中,目前数据可视化的第三方库还是特别少,go-echarts 的开发就是为了填补这部分的空隙。Apache ECharts 是非常优秀的可视化图表库,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。也有其他语言为其实现了相应语言版本的接口,如 Python 的 pyecharts,go-echarts 也是借鉴了 pyecharts 的一些设计思想。

特性

  • 简洁的 API 设计,使用如丝滑般流畅
  • 囊括了 25+ 种常见图表,应有尽有
  • 高度灵活的配置项,可轻松搭配出精美的图表
  • 详细的文档和示例,帮助开发者更快的上手项目
  • 多达 400+ 地图,为地理数据可视化提供强有力的支持

如何使用

1.安装go-echarts库

go get -u github.com/go-echarts/go-echarts/v2

2.接着,我们来创建一个图表实例,绘制一个条形图,来演示如何使用。

package main

import (
	"math/rand"
	"os"

	"github.com/go-echarts/go-echarts/v2/charts"
	"github.com/go-echarts/go-echarts/v2/opts"
)

// generate random data for bar chartg
func generateBarItems() []opts.BarData {
	items := make([]opts.BarData, 0)
	for i := 0; i < 7; i++ {
		items = append(items, opts.BarData{Value: rand.Intn(300)})
	}
	return items
}

func main() {
	//创建一个新的bar实例
	bar := charts.NewBar()
	//设置一些全局选项,如标题/图例/工具提示或其他任何东西
	//example: 标题/子标题
	bar.SetGlobalOptions(charts.WithTitleOpts(opts.Title{
		Title:    "My first bar chart generated by go-echarts",
		Subtitle: "It's extremely easy to use, right?",
	}))

	//将数据放入实例
	bar.SetXAxis([]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}).
		AddSeries("Category A", generateBarItems()).
		AddSeries("Category B", generateBarItems())
	//奇迹发生的地方-绘图生成html
	f, _ := os.Create("bar.html")
	bar.Render(f)
}

运行上例,会生成bar.html,详细:

  1. 如果想把上例改成折线图呢
package main

import (
	"github.com/go-echarts/go-echarts/v2/charts"
	"github.com/go-echarts/go-echarts/v2/opts"
	"math/rand"
	"os"
)

// generate random data for line chart
func generateLineItems() []opts.LineData {
	items := make([]opts.LineData, 0)
	for i := 0; i < 7; i++ {
		items = append(items, opts.LineData{Value: rand.Intn(300)})
	}
	return items
}

func main() {
	//创建一个折线图实例
	line := charts.NewLine()
	//设置一些全局选项,如标题/图例/工具提示或其他任何东西
	//example: 标题/子标题
	line.SetGlobalOptions(charts.WithTitleOpts(opts.Title{
		Title:    "My first lines chart generated by go-echarts",
		Subtitle: "It's extremely easy to use, right?",
	}))

	//将数据放入实例
	line.SetXAxis([]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}).
		AddSeries("Category A", generateLineItems()).
		AddSeries("Category B", generateLineItems()).
		SetSeriesOptions(charts.WithLineChartOpts(opts.LineChart{Smooth: true}))

	//奇迹发生的地方-绘图生成html
	f, _ := os.Create("lines.html")
	line.Render(f)
}

运行上例,会生成lines.html,详细:

  1. 如果想直接生成图表的 http serve呢:
package main

import (
	"github.com/go-echarts/go-echarts/v2/charts"
	"github.com/go-echarts/go-echarts/v2/opts"
	"math/rand"
	"net/http"
)

// generate random data for line chart
func generateLineItems() []opts.LineData {
	items := make([]opts.LineData, 0)
	for i := 0; i < 7; i++ {
		items = append(items, opts.LineData{Value: rand.Intn(300)})
	}
	return items
}

func httpserver(w http.ResponseWriter, _ *http.Request) {
	//创建一个折线图实例
	line := charts.NewLine()
	//设置一些全局选项,如标题/图例/工具提示或其他任何东西
	//example: 标题/子标题
	line.SetGlobalOptions(charts.WithTitleOpts(opts.Title{
		Title:    "My first lines chart generated by go-echarts",
		Subtitle: "It's extremely easy to use, right?",
	}))

	//将数据放入实例
	line.SetXAxis([]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}).
		AddSeries("Category A", generateLineItems()).
		AddSeries("Category B", generateLineItems()).
		SetSeriesOptions(charts.WithLineChartOpts(opts.LineChart{Smooth: true}))

	//奇迹发生的地方-绘图生成html
	line.Render(w)
}

func main() {
	http.HandleFunc("/", httpserver)
	http.ListenAndServe(":8080", nil)
}

访问,http://localhost:8080/ ,详细如下

三、总结

本文简要介绍go-echarts和使用方法,并通过绘制条形图、折线图和图表的http server 的例子演示了使用方法。更多功能待续。

参考

Apache APISIX 是 Apache 软件基金会下的云原生 API 网关,它兼具动态、实时、高性能等特点,提供了负载均衡、动态上游、灰度发布(金丝雀发布)、服务熔断、身份认证、可观测性等丰富的流量管理功能。很多场景下,我们可以选择在APISIX中使用CORS(跨源资源共享)来解决跨域问题。

APISIX提供了CRD和Annotations模式来启用CORS。本文主要介绍如何在APISIX Ingress Annotations模式下启用CORS。

环境:

应用 信息
Kubernetes v1.25.7
apisix apache/apisix:3.4.0-debian
apisix-ingress-controller apache/apisix-ingress-controller:1.6.0

IngressAnnotations模式

IngressAnnotations模式,可以通过在Ingress资源上添加注释来配置APISIX的行为。比如添加一个CORS注释来指示APISIX启用CORS:

annotations
    k8s.apisix.apache.org/enable-cors: "true"
    kubernetes.io/ingress.class: apisix

接着,我们以给一个Http服务my-service, 创建了一个Ingress资源的例子来演示, Ingress详细如下:

创建Ingress

创建my-service的ingress资源:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: match-ingress
  namespace: my-ns
  annotations:
    k8s.apisix.apache.org/enable-cors: "true"
    kubernetes.io/ingress.class: apisix
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: my-service
            port:
              number: 8000
        path: /v2
        pathType: Prefix

在这个示例中,我们添加了【k8s.apisix.apache.org/enable-cors: “true”】的注释, 即启用cors功能,默认允许来自任何源的请求访问my-service的API。

Dashboard启用CORS

打开APISIX Dashboard界面:在【插件】中查找“CORS” 。单击该插件,然后单击“启用”。

然后,可以在路由点击查看my-service的对应路由,如下:

{
  "uris": [
    "/v2/",
    "/v2/*"
  ],
  "name": "ing_my-service-ingress_cfc81dbe",
  "desc": "Created by apisix-ingress-controller, DO NOT modify it manually",
  "plugins": {
    "cors": {
      "allow_credential": false,
      "allow_headers": "*",
      "allow_methods": "*",
      "allow_origins": "*",
      "expose_headers": "*",
      "max_age": 5
    }
  },
  "upstream_id": "7c630009",
  "labels": {
    "managed-by": "apisix-ingress-controller"
  },
  "status": 1
}

plugins.cors为跨域的对应规则配置,说明插件启用成功。

测试

然后我们向my-service的发起一个测试请求:

curl -i -k 'http://[my-cluster-ip:nodeport]/v2/my-service/api/geeker'   -X OPTIONS \
   -H "Access-Control-Allow-Origin: *" \
   -H "Access-Control-Request-Method: POST" \
   -H "Access-Control-Request-Headers: content-type" \
   -H "Origin: http://localhost/" 
   

输出:

HTTP/1.1 200 OK
Date: Sat, 15 Jul 2023 13:53:57 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.4.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: *
Access-Control-Max-Age: 5
Access-Control-Expose-Headers: *
Access-Control-Allow-Headers: *

如果返回结果中出现 CORS 相关的 header( ccess-Control-Allow-Origin: * < Access-Control-Allow-Methods: * < Access-Control-Allow-Headers: * < Access-Control-Expose-Headers: * < Access-Control-Max-Age: 5 ),则代表插件生效、跨域成功。

遇到的问题

在增加好ingress后测试API,发现没有CORS 相关的 header

,跨域失败

HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Origin: *
Date: Sat, 15 Jul 2023 13:46:16 GMT
Server: APISIX/3.4.0

经检查,是ApiSix的config配置有误,在配置的插件里,开启了其他插件,没有包括cors,导致插件不生效。去掉后,重启插件配置生效、跨域成功。

plugins:
  # the plugins you enabled
  - log-rotate
  - proxy-rewrite

参考

本文主要记录通过云盘实例的备份数据转换成的SQL文件, 恢复到PostgreSQL自建数据库的过程和遇到的问题。

恢复过程主要包括:

  1. 下载备份:通过生成的外部连接下载
  2. 下载python脚本restore_pg.py
  3. 执行脚本恢复到PostgreSQL

接着,下文将描述恢复到数据库:fooDatabase表:geeker的过程。

下载备份

  1. 通过下载备份功能将云盘实例的备份文件转换成CSV文件或SQL文件下载到本地或ECS

  1. 解压下载的文件。 解压缩命令格式为 tar -zxvf<压缩包文件名>.tar.gz -C <解压缩后的文件位置> 。 示例如下:
# tar -zxvf fooDatabase.tar.gz -C ./home/abs/exports

解压后的文件目录

#tree
|____.DS_Store
|____fooDatabase
| |____structure.sql
| |____public
| | |____geeker
| | | |____column_structure.sql
| | | |____constraint_structure.sql
| | | |____data
| | | | |____0-1.sql
| | |____structure.sql
  • ./fooDatabase/structure..sql 为创建数据库,详细:
CREATE DATABASE fooDatabase;
\c fooDatabase;
  • ./fooDatabase/public/structure..sql 创建模式, 详细 :
CREATE SCHEMA IF NOT EXISTS "public"

下载restore_pg.py

  1. 下载Python脚本。https://static-aliyun-doc.oss-cn-hangzhou.aliyuncs.com/file-manage-files/zh-CN/20230111/lvmx/restore_pg.py?spm=a2c4g.456306.0.0.14f359aeqChZwi&file=restore_pg.py

ps: 如果数据名包含大写,需要修改脚本。可以参考https://github.com/easytking/database/blob/main/restore_pg.py

  1. 执行如下命令对Python脚本文件restore_pg.py添加执行权限。
# chmod +x ./restore_pg.py

恢复到数据库

搭建数据库

这里使用Dockers搭建

version: '3.5' 

services:
  pg2:
    image: "postgres:14"
    container_name: pg2
    restart: always   
    ports:
      - "5432:5432"       
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=123456
      - POSTGRES_DB=test
      - PGDATA=/var/lib/postgresql/data
    volumes:
      - ./data/pg2/postgresql:/var/lib/postgresql/data  
Postgres客户端安装

macOS下使用brew安装:

brew install postgres

执行脚本恢复

执行如下命令将CSV文件或SQL文件恢复至目标数据库。 命令格式为

python3 restore_pg.py <CSV文件或SQL文件路径> <数据库主机> <数据库端口> <数据库账号> <数据库密码>

例如:

python3 restore_pg.py /exports  127.0.0.1 5432 postgres 123456

此时,127.0.0.1:5432将会新增数据fooDatabase,表geeker。

Succee。

遇到的问题-数据库名大小写

由于的数据库名中包含大写【fooDatabase】,在导入的过程中,抛出错误:

restore structure database
[ERROR]: execute SQL failed. command: 

在 PostgreSQL 中,数据库名称默认情况下是不区分大小写的,但是在创建数据库时,如果您使用了双引号引用数据库名称,则 PostgreSQL 会将其视为区分大小写的标识符。

解决方法1: 手动修改Database/structure.sql

或者手动建库的sql: ./exports/structure.sql

CREATE DATABASE fooDatabse;
\c fooDatabse;

修改为

CREATE DATABASE "fooDatabse";
\c fooDatabse;

解决方法2: 修改python脚本

分析一些restore_pg.py ,主要包含了以下方法:

create_database:创建数据库。
create_schema:创建模式。
import_file_csv:从 CSV 文件导入数据。
import_file_sql:从 SQL 文件导入数据。
print_usage:输出脚本使用方法。
__main__ 脚本的主要逻辑是,遍历备份数据目录,对每个数据库和每个模式执行相应的恢复操作,包括创建数据库、创建模式、创建表和导入数据。在导入数据时,脚本支持从 CSV 文件和 SQL 文件中导入数据。同时,脚本还支持导入表的约束、索引和继承关系等结构信息。

脚本的运行需要传入 5 个参数,分别是备份数据所在目录、数据库主机名、数据库端口、数据库用户名和数据库密码。如果参数不足 5 个,则会输出使用方法并退出。

那么显然我们需要修改的是create_database,原始的是:

def create_database(db_host, db_port, db_user, db_pass, db_name, create_stmt_file):
    if db_name == "postgres":
        return
    cmd = "PGPASSWORD=" + db_pass + " psql -h" + db_host + " -p" + db_port + " -U" + db_user + " -d" + "postgres" + " <" + create_stmt_file
    if os.system(cmd) != 0:
        print("[ERROR]: execute SQL failed. command: " + cmd)
        exit(1)

要修改上面的 Python 代码以区分大小写地创建数据库,您需要在使用 psql 命令创建数据库时将数据库名称用双引号引用起来。以下是修改后的代码:

def create_database(db_host, db_port, db_user, db_pass, db_name, create_stmt_file):
    if db_name.lower() == "postgres":  # 将 db_name 转换为小写,以便检查是否为 "postgres"
        return
    cmd = 'PGPASSWORD="' + db_pass + '" psql -h ' + db_host + ' -p ' + db_port + ' -U ' + db_user + ' -d postgres -c "CREATE DATABASE \\"' + db_name + '\\";"'
    if os.system(cmd) != 0:
        print("[ERROR]: execute SQL failed. command: " + cmd)
        exit(1)

完成脚本上传到:https://github.com/easytking/database/blob/main/restore_pg.py

参考

背景:

sealos安装的Kubernetes集群,删除后增加节点失败。

Error: failed to add hosts: run command /var/lib/sealos/data/default/rootfs/opt/sealctl hosts add --ip 10.0.0.Master --domain sealos.hub on 10.0.0.Node:22, output: , error: Process exited with status 139 from signal SEGV,

环境:

应用 信息
os Ubuntu
sealos v4.2.2
Kubernetes v1.25.7

问题1:文件时md5校验失败

操作步骤:

sealos delete --nodes 10.0.0.X 
sealos add --nodes 10.0.0.X 

执行报错:

sha256 sum not match

Sealos 的 –debug 参数是一个全局参数,用于开启调试模式,以便在出现问题时能更详细地了解系统的运行情况。

sealos add --nodes 10.0.0.X  --debug

Sealos传输文件时比较md5查看是否报错,issues给了一个解决办法:可以关闭校验。

#  export SEALOS_SCP_CHECKSUM=false

修改完,出现了另外一个问题:

问题2:sealctl 增加host失败

Error: failed to add hosts: run command /var/lib/sealos/data/default/rootfs/opt/sealctl hosts add --ip 10.0.0.Master --domain sealos.hub on 10.0.0.Node:22, output: , error: Process exited with status 139 from signal SEGV,

 debug模式查看详细

2023-06-28T09:56:43 debug show registry info, IP: 10.0.0.X:22, Domain: sealos.hub, Data: /var/lib/registry2023-06-28T09:56:43 debug start to exec /var/lib/sealos/data/default/rootfs/opt/sealctl hosts add --ip 10.0.0.X --domain sealos.hub on 10.0.0.M:222023-06-28T09:56:44 error Applied to cluster error: failed to add hosts: run command /var/lib/sealos/data/default/rootfs/opt/sealctl hosts add --ip 10.0.0.X --domain sealos.hub on 10.0.0.M:22, output: , error: Process exited with status 139 from signal SEGV,2023-06-28T09:56:44 debug save objects into local: /root/.sealos/default/Clusterfile, objects: [apiVersion: apps.sealos.io/v1beta1kind: Cluster

可见,是seactl执行"sealctl hosts add –ip 10.0.0.X –domain sealos.hub" 失败。此文件是从master拷贝过来的:

/var/lib/containers/storage/overlay/5dd64dde7bc046cfd7554458e2950c41c0d86536e4e96532cfa2ee60685404d4/merged/opt to dst /var/lib/sealos/data/default/rootfs/opt2023-06-28T09:56:40 debug remote copy files src /var/lib/containers/storage/overlay/44ce75c1b3e483c61ecfbc07a72f87da8627ce40d9df9451f2d04dfad8dffc65/merged/opt to dst /var/lib/sealos/data/default/rootfs/opt2023-06-28T09:56:42 debug remote copy files src

我们试着手动拷贝过去,执行是正常的。初步判断是传输的文件出错 ,issure沟通,建议试着“scp进程可能有问题,比较md5查看是否有问题。export SEALOS_SCP_CHECKSUM = true”,还是出现 “sha256 sum not match”。无效。

解决

这个v4.2.2 的一个bug,希望下个版本能修复。最终一个临时处理办法:重置集群

# sealos reset 

我尝试重置集群,再加入node是可行的。

小结

本文主要记录排查、解决【sealos-v4.2.2删除节点后再增加节点失败】的过程,原因sealos的版本v4.2.2 问题,,其一,旧版本在拷贝seactl文件到node时会抛出失败:sha256比较文件时,经排查判断是传输过程文件出错了。其二,可以临时通过重置集群来恢复。 希望对你有所帮助。

参考

Metrics Server是一个可扩展的、高效的容器资源度量源,用于Kubernetes内置的自动伸缩管道。

kubectl top 可以查看node、pod 的实时资源使用情况:如CPU、内存。但需要预装 Metrics Server ,不然会出现如下错误提示:

kubectl top pods
error: Metrics API not available

Metrics Server 从 Kubelet 收集资源指标,并通过Metrics API在 Kubernetes apiserver 中公开, 以供Horizo​​ntal Pod Autoscaler和Vertical Pod Autoscaler使用。指标 API 也可以通过 访问kubectl top,从而更容易调试自动缩放管道。

今天主要介绍安装的过程,以及记录安装过程遇到一些问题。

开始安装

这里我们采用官网给的清单 components.yaml安装最新的 Metrics Server 版本:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

查看是否安装成功:

# kubectl get deployment metrics-server -n kube-system
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
metrics-server   0/1     1            0           118s

发现安装失败。

问题1:镜像拉取失败

查看pod详细信息:

# kubectl get pod  -n kube-system
NAME                              READY   STATUS         RESTARTS      AGE
metrics-server-55c774cdbb-jmfz8   0/1     ErrImagePull   0             2m34s

查看pod日志:

# kubectl logs pods/metrics-server-55c774cdbb-jmfz8 -n kube-system
Error from server (BadRequest): container "metrics-server" in pod "metrics-server-55c774cdbb-jmfz8" is waiting to start: trying and failing to pull image

从日志可知是镜像拉取不下来,我们可以修改镜像地址。可以从dockerhub,查找最新版本,如作者安装的版本是:metrics-server:v0.6.1。打开components.yaml , 修改Deployment,spec.containers.coimage 为搜索到的,如“registry.aliyuncs.com/google_containers/metrics-server:v0.6.1",修改完详细如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  strategy:
    rollingUpdate:
      maxUnavailable: 0
  template:
    metadata:
      labels:
        k8s-app: metrics-server
    spec:
      containers:
      - args:
        - --cert-dir=/tmp
        - --secure-port=4443
        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
        - --kubelet-use-node-status-port
        - --metric-resolution=15s
        image: registry.aliyuncs.com/google_containers/metrics-server:v0.6.1

更新部署deployment:

# kubectl delete  deployment metrics-server -n kube-system
deployment.apps "metrics-server" deleted

# kubectl apply -f components.yaml

问题2:证书

更新后,发现另外一个问题:

# server.go:132] unable to fully scrape metrics: unable to fully scrape metrics from node docker-desktop: unable to fetch metrics from node docker-desktop: Get "https://192.168.1.88:10250/stats/summary?only_cpu_and_memory=true": x509: cannot validate certificate for 192.168.1.88 because it doesn't contain any IP SANs

从日志可知是权限验证(证书)出了问题,通过搜索github issues ,issue回复里提供了一个解决方法是在可以关闭证书验证,详细如下:

修改清单components.yam, 增加:

- --kubelet-insecure-tls

修改完 Deplyment详细如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  strategy:
    rollingUpdate:
      maxUnavailable: 0
  template:
    metadata:
      labels:
        k8s-app: metrics-server
    spec:
      containers:
      - args:
        - --cert-dir=/tmp
        - --secure-port=4443
        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
        - --kubelet-use-node-status-port
        - --kubelet-insecure-tls
        - --metric-resolution=15s

其中:

–kubelet-preferred-address-types: 优先使用 InternalIP 来访问 kubelet,这样可以避免节点名称没有 DNS 解析记录时,通过节点名称调用节点 kubelet API 失败的情况(未配置时默认的情况);

–kubelet-insecure-tls: kubelet 的 10250 端口使用的是 https 协议,连接需要验证 tls 证书。–kubelet-insecure-tls 不验证客户端证书。

然后,检查是否启动成功:

#kubectl get deployment metrics-server -n kube-system
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
metrics-server   1/1     1            1           14m

success。

参考

背景:

在导入外部集群到rancher时,cattle-cluster-agent报错

level=fatal msg="looking up cattle-system/cattle ca/token: no secret exists for service account cattle-system/cattle"

环境:

应用 信息
Kubernetes v1.25.7
Rancher v2.6.6

解决

经查,K8s在版本v1.24启用新测试功能特性-LegacyServiceAccountTokenNoAutoGeneration,默认是开启的。

服务账号(Service Account)是一种自动被启用的用户认证机制,使用经过签名的持有者令牌来验证请求。

服务账号通常由 API 服务器自动创建并通过 ServiceAccount 准入控制器关联到集群中运行的 Pod 上。 持有者令牌会挂载到 Pod 中可预知的位置,允许集群内进程与 API 服务器通信。 服务账号也可以使用 Pod 规约的 serviceAccountName 字段显式地关联到 Pod 上。

我们可以通过k8s 组件上指定的各种特性开关控制 - 【特性门控是描述 Kubernetes 特性的一组键值对。你可以在 Kubernetes 的各个组件中使用 –feature-gates 标志来启用或禁用这些特性。】 关闭该特性:

打开 /etc/kubernetes/manifest/kube-controller-manager.yml的 spec.container.command中增加

- --feature-gates=LegacyServiceAccountTokenNoAutoGeneration=false

修改完,例如:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    component: kube-controller-manager
    tier: control-plane
  name: kube-controller-manager
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-controller-manager
    - --allocate-node-cidrs=true
    - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --bind-address=0.0.0.0
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --cluster-cidr=1.2.0.0/10
    - --cluster-name=kubernetes
    - --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt
    - --cluster-signing-key-file=/etc/kubernetes/pki/ca.key
    - --controllers=*,bootstrapsigner,tokencleaner
    - --feature-gates=EphemeralContainers=true
    - --kubeconfig=/etc/kubernetes/controller-manager.conf
    - --leader-elect=true
    - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
    - --root-ca-file=/etc/kubernetes/pki/ca.crt
    - --service-account-private-key-file=/etc/kubernetes/pki/sa.key
    - --service-cluster-ip-range=10.88.0.0/22
    - --use-service-account-credentials=true
    - --feature-gates=LegacyServiceAccountTokenNoAutoGeneration=false
    ...

重启 kube-controller-manager,ps:如果是多个master,需要每个都修改。

小结

本文记录导入外部集群到rancher时,遇到的用户认证密钥不正确的问题。回顾k8s在1.23~1.25的更新日志,以及查阅issues,原因是1.24启用了新测试特性LegacyServiceAccountTokenNoAutoGeneration,该特性使用经过签名的持有者令牌来验证请求。可以通过k8s组件上指定的各种特性开关控制来关闭此特性来解决报错,最终正常导入到rancher。

参考

在数据的传输过程,为了安全,我们需要对其进行加密。加密算法分为双向加密和单向加密。单向加密包括MD5、SHA等摘要算法,它们是不可逆的。双向加密包括对称加密和非对称加密,对称加密包括AES加密、DES加密等。双向加密是可逆的,它使用相同的密钥进行加密和解密。本文主要介绍AES算法以及如何使用golang实现AES加解密。

AES简介

AES是高级加密标准,在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,目前已经被全世界广泛使用,同时AES已经成为对称密钥加密中最流行的算法之一。AES支持三种长度的密钥:128位,192位,256位。

加解密过程为:字节代替、行移位、列混淆、轮密钥加。

相关概念:

  1. 密钥:AES128,AES192,AES256,实际上就是指的AES算法对不同长度密钥的使用

  2. 分组: 把明文拆分成一个个独立的明文块,每一个明文块长度128bit。加密生成一个个独立的密文块,这些密文块拼接在一起。填充模式有:

    1. NoPadding :不做任何填充,但是要求明文必须是16字节的整数倍。
    2. PKCS5Padding: 如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字符,且每个字节的值等于缺少的字符数。
    3. ISO10126Padding: 如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字节,最后一个字符值等于缺少的字符数,其他字符填充随机数
  3. 加密模式:

    1. 电码本模式(Electronic Codebook Book (ECB)) 模式是将整个明文分成若干段相同的小段,然后对每一小段进行加密。
    2. 密码分组链接模式(Cipher Block Chaining (CBC)) 模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算,再与密钥进行加密
    3. 计算器模式(Counter (CTR))不常用,在 CTR 模式中, 有一个自增的算子,这个算子用密钥加密之后输出和明文异或的结果得到密文,相当于一次一密
    4. 密码反馈模式(Cipher FeedBack (CFB))不常用
    5. 输出反馈模式(Output FeedBack (OFB))

golang实现AES加解密

golang使用“crypto/aes”进行AES加解密 。接着我们通过一个使用CBC模式对原文进行加解密的例子来演示如何实现。

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "fmt"
)

func main() {
    // 原始数据
    plaintext := []byte("Hello, world!")

    // 密钥,长度必须为 16、24 或 32 字节(128位,192位,256位)
    key := []byte("0123456789abcdef")

    // 使用 AES 创建加密算法对象
    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    // 对数据进行填充,使其长度为块大小的整数倍
    plaintext = PKCS5Padding(plaintext, block.BlockSize())

    // 创建 CBC 模式的加密算法对象
    iv := []byte("0123456789abcdef") // 初始化向量,长度必须为块大小
    mode := cipher.NewCBCEncrypter(block, iv)

    // 加密数据
    ciphertext := make([]byte, len(plaintext))
    mode.CryptBlocks(ciphertext, plaintext)

    // 将密文进行 base64 编码
    encoded := base64.StdEncoding.EncodeToString(ciphertext)
    fmt.Println(encoded)

    // 解码 base64 编码的密文
    decoded, _ := base64.StdEncoding.DecodeString(encoded)

    // 创建 CBC 模式的解密算法对象
    mode = cipher.NewCBCDecrypter(block, iv)

    // 解密数据
    decrypted := make([]byte, len(decoded))
    mode.CryptBlocks(decrypted, decoded)

    // 去除填充数据
    decrypted = PKCS5UnPadding(decrypted)
    fmt.Println(string(decrypted))
}

// 对数据进行填充,使其长度为 blockSize 的整数倍
func PKCS5Padding(data []byte, blockSize int) []byte {
    padding := blockSize - len(data)%blockSize
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(data, padtext...)
}

// 去除填充数据
func PKCS5UnPadding(data []byte) []byte {
    length := len(data)
    unpadding := int(data[length-1])
    return data[:(length - unpadding)]
}

输出:

+HzZQh0Do42Kg1PQsdhdcw==
Hello, world!

上例中,分别演示了

  1. 对加密过程:创建加密对象、原文填充、创建CBC模式,加密。
  2. 解密过程: 创建CBC解密模式,解密,去除填充。

为了方便使用,我们对上例的加解密进行封装,详细如下:

package main

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"errors"
	"fmt"
)

func main() {
	plaintext := []byte("Hello, world!")
	key := []byte("0123456789abcdef")
	iv := []byte("0123456789abcdef")

	// 加密数据
	encoded, err := AesCBCEncrypt(plaintext, key, iv)
	if err != nil {
		panic(err)
	}
	fmt.Println(encoded)

	// 解密数据
	decoded, err := AesCBCDecrypt(encoded, key, iv)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(decoded))
}

// AesCBCEncrypt 使用 AES CBC 模式加密数据
func AesCBCEncrypt(plaintext, key, iv []byte) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}

	plaintext = PKCS5Padding(plaintext, block.BlockSize())

	mode := cipher.NewCBCEncrypter(block, iv)

	ciphertext := make([]byte, len(plaintext))
	mode.CryptBlocks(ciphertext, plaintext)

	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// AesCBCDecrypt 使用 AES CBC 模式解密数据
func AesCBCDecrypt(ciphertext string, key, iv []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	decoded, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		return nil, err
	}

	if len(decoded)%block.BlockSize() != 0 {
		return nil, errors.New("ciphertext is not a multiple of the block size")
	}

	mode := cipher.NewCBCDecrypter(block, iv)

	decrypted := make([]byte, len(decoded))
	mode.CryptBlocks(decrypted, decoded)

	decrypted = PKCS5UnPadding(decrypted)

	return decrypted, nil
}

// PKCS5Padding 对数据进行填充,使其长度为 blockSize 的整数倍
func PKCS5Padding(data []byte, blockSize int) []byte {
	padding := blockSize - len(data)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(data, padtext...)
}

// PKCS5UnPadding 去除填充数据
func PKCS5UnPadding(data []byte) []byte {
	length := len(data)
	unpadding := int(data[length-1])
	return data[:(length - unpadding)]
}

总结

本文主要介绍对称加密算法AES,AES使用不同指定长度的密钥,对原文进行分组,可选用更安全的加密模式CBC。在 golang中,可以使用“crypto/aes”快速实现原文填充、加密模式、加密、解密、去除填充等AES加解密过程。

参考

背景:

sealos安装的Kubernetes集群,master节点出现大量端口占用。

环境:

应用 信息
os Ubuntu
sealos v4.1.7
Kubernetes v1.23.9

排查问题

按照本地端口对输出进行排序

netstat -anp | grep ESTABLISHED | awk '{print $4}' | sort | uniq -c | sort -n
    28 10.0.0.101:6443
    111 127.0.0.1:2379
  9009 10.0.0.101:5000

查找本地端口对应的应用程序

  lsof -i :5000
  image-cri 2811249 root 4120u  IPv4 291818320      0t0  TCP master1:41710->master1:5000 (ESTABLISHED)

可见,是image-cri-shim占用的端口数。

image-cri-shim 工作原理

image-cri-shim 是一个基于 CRI (Container Runtime Interface) 和 kubelet 的 gRPC (Google Remote Procedure Call) shim。CRI 是 Kubernetes 中用于与容器运行时进行交互的接口,而 kubelet 是负责维护容器运行状态和节点级别的资源管理的 Kubernetes 组件。

常用操作:

  • 启动服务: systemctl start image-cri-shim
  • 停止服务: systemctl stop image-cri-shim
  • 重启服务: systemctl restart image-cri-shim
  • 查看服务状态: systemctl status image-cri-shim
  • 参考日志: journalctl -u image-cri-shim -f

重现问题

  1. 在测试环境安装相同版本的sealos版本4.1.7,这里就不赘述,可以参考(https://sealos.io/zh-Hans/docs/getting-started/kuberentes-life-cycle)

  2. 安装好之后,发现image-cri-shim的版本(4.1.3)不对,

image-cri-shim --version
image-cri-shim version 4.1.3-ed0a75b9
  1. 更新image-cri-shim版本为4.1.7
wget https://github.com/labring/sealos/releases/download/v4.1.7/sealos_4.1.7_linux_amd64.tar.gz && tar xvf sealos_4.1.7_linux_amd64.tar.gz.1 image-cri-shim
sealos exec -r master,node "systemctl stop image-cri-shim"
sealos scp "./image-cri-shim" "/usr/bin/image-cri-shim"
sealos exec -r master,node "systemctl start image-cri-shim"
sealos exec -r master,node "image-cri-shim -v"

输出类似以下内容,表示成功:

image-cri-shim version 4.1.7-ed0a75b9
192.168.1.101:22: image-cri-shim version 4.1.7-ed0a75b9
192.168.1.102:22: image-cri-shim version 4.1.7-ed0a75b9
  1. 启动后,观察日志:
root@master1:~# journalctl -u image-cri-shim -f
-- Logs begin at Wed 2023-04-19 22:42:32 UTC. --
May 25 06:49:38 master1 image-cri-shim[1116090]: 2023-05-25T06:49:38 info actual imageName: pause:3.6
May 25 06:49:38 master1 image-cri-shim[1116090]: 2023-05-25T06:49:38 info image: k8s.gcr.io/pause:3.6, newImage: sealos.hub:5000/pause:3.6, action: ImageStatus
May 25 06:54:38 master1 image-cri-shim[1116090]: 2023-05-25T06:54:38 info actual imageName: pause:3.6
May 25 06:54:38 master1 image-cri-shim[1116090]: 2023-05-25T06:54:38 info image: k8s.gcr.io/pause:3.6, newImage: sealos.hub:5000/pause:3.6, action: ImageStatus
May 25 06:59:38 master1 image-cri-shim[1116090]: 2023-05-25T06:59:38 info actual imageName: pause:3.6
May 25 06:59:38 master1 image-cri-shim[1116090]: 2023-05-25T06:59:38 info image: k8s.gcr.io/pause:3.6, newImage: sealos.hub:5000/pause:3.6, action: ImageStatus
  1. 监控端口数的变化
netstat -na | grep 5000 | wc -l
96
116
120
...
  1. 可见,每个5分钟,占用的端口数就增加20左右。问题复现了,接着我们来看看怎么处理。

更新版本为4.2.0

从sealos的github的issues有提供一个修复方案:升级image-cri-shim的版本为4.2.0

wget https://github.com/labring/sealos/releases/download/v4.2.0/sealos_4.2.0_linux_amd64.tar.gz && tar xvf sealos_4.2.0_linux_amd64.tar.gz image-cri-shim
sealos exec -r master,node "systemctl stop image-cri-shim"
sealos scp "./image-cri-shim" "/usr/bin/image-cri-shim"
sealos exec -r master,node "systemctl start image-cri-shim"
sealos exec -r master,node "image-cri-shim -v"

输出如下,表示成功:

image-cri-shim version 4.2.0-f696a621
192.168.1.101:22: image-cri-shim version 4.2.0-f696a621
192.168.1.102:22: image-cri-shim version 4.2.0-f696a621

观察是否已修复

  1. 观察inamge-cri-shim日志:
root@master1:~# journalctl -u image-cri-shim -f
-- Logs begin at Wed 2023-04-19 22:42:32 UTC. --
May 25 07:44:09 master1 image-cri-shim[1159432]: 2023-05-25T07:44:09 info Timeout: {15m0s}
May 25 07:44:09 master1 image-cri-shim[1159432]: 2023-05-25T07:44:09 info criRegistryAuth: map[]
May 25 07:44:09 master1 image-cri-shim[1159432]: 2023-05-25T07:44:09 info criOfflineAuth: map[sealos.hub:5000:{Username:admin Password:passw0rd Auth: Email: ServerAddress:http://sealos.hub:5000 IdentityToken: RegistryToken:}]
May 25 07:44:09 master1 image-cri-shim[1159432]: 2023-05-25T07:44:09 info socket info shim: /var/run/image-cri-shim.sock ,image: /run/containerd/containerd.sock, registry: http://sealos.hub:5000
May 25 07:44:09 master1 image-cri-shim[1159432]: 2023-05-25T07:44:09 info changed ownership of socket "/var/run/image-cri-shim.sock" to root/root
May 25 07:44:09 master1 image-cri-shim[1159432]: 2023-05-25T07:44:09 info changed permissions of socket "/var/run/image-cri-shim.sock" to -rw-rw----
May 25 07:44:41 master1 image-cri-shim[1159432]: 2023-05-25T07:44:41 info image: k8s.gcr.io/pause:3.6, newImage: sealos.hub:5000/pause:3.6, action: ImageStatus
May 25 07:49:42 master1 image-cri-shim[1159432]: 2023-05-25T07:49:42 info image: k8s.gcr.io/pause:3.6, newImage: sealos.hub:5000/pause:3.6, action: ImageStatus
May 25 07:54:42 master1 image-cri-shim[1159432]: 2023-05-25T07:54:42 info image: k8s.gcr.io/pause:3.6, newImage: sealos.hub:5000/pause:3.6, action: ImageStatus
  1. 监控端口数的变化
root@master1:~# netstat -na | grep 5000 | wc -l
1 
6 
1
  1. 可见,请求是还有,但并没有增加端口数,看来问题修复了。

ps: 我们重现问题的Kubenetes版本是v1.23.9,在实际开发中发现其他版本的v1.25.7也会出现类似问题,验证此版本待后续更新。

小结

本文主要记录排查、复现、处理【sealos安装的Kubenetes集群master节点大量占用端口】的过程,原因系image-cri-shim的版本问题,旧版本会频繁请求master,导致创建大量的连接,占用端口数。通过升级image-cri-shim版本可以解决该问题。

参考

Nginx-Proxy-Manager 是Nginx Web GUI( 可视化管理界面),为新手快速设置反向代理提供帮助。 它的主要目标是:

我创建这个项目是为了满足个人需要,为用户提供一种简单的方法来完成带有 SSL 终止的反向代理主机,而且它必须非常简单,以至于猴子都能做到。这个目标没有改变。虽然可能有高级选项,但它们是可选的,并且项目应该尽可能简单,以便进入这里的门槛很低。

feature

它主要的功能包括以下几个:

  • 基于Tabler的美观安全的管理界面
  • 在对 Nginx 一无所知的情况下轻松创建转发域、重定向、流和 404 主机
  • 使用 Let’s Encrypt 的免费 SSL 或提供您自己的自定义 SSL 证书
  • 主机的访问列表和基本 HTTP 身份验证
  • 超级用户可用的高级 Nginx 配置
  • 用户管理、权限和审计日志

安装

这里我们Docker Compose安装启动。

  1. 你需要预先安装了Docker Compose 环境。
  2. 创建docker-compose.yml
version: '3.8'
services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '8080:80'
      - '81:81'
      - '8443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
  1. 启动
docker-compose -d
  1. Dashboard

访问web界面 http://127.0.0.1:81

这么默认的用户名和密码是:

Email:    admin@example.com
Password: changeme

登入后,按提示重新设置用户名、密码即可。

  1. 主页面 home page

如成功登入,可以看到主页面如下:

实战 :反向代理Nginx-Proxy-Manage后台

如上述中,我们通过81端口可以访问GUI,下面,我们将通过反向代理,可以直接通过 http://your-site:8080即可访问GUI。

  1. 点击 【Proxy Hosts】,在界面Details选项添加如下图所示:

  1. 访问 http://your-site:8080

说明成功了

  1. 如果想要关闭代理,点击编辑【Proxy Host】 的 Enable,详细如下

  2. 再访问,会跳出404界面

以上。

小结

本文主要简介Nginx可视化管理平台Nginx-Proxy-Manager及主要功能,为配置强大Nginx代理功能降低门槛。通过一个实战案例:代理Manager后台,演示了基础使用方法。

参考

本文主要介绍享元模式基础概念、实现原理和通过一个棋牌游戏例子简述实现过程。

享元模式

所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。 具体来讲,当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。

享元的实现并不复杂,通过工厂模式,通过一个 Map 来缓存已经创建过的享元对象,来达到复用的目的。 其中:

  • Flyweight : 享元类包含原始对象中部分能在多个对象中共享的状态。
  • FlyweightFactory:享元工厂,会对已有享元的缓存池进行管理。

接着,我们通过一个棋牌游戏的案例来演示。

案例-棋牌游戏

这里以象棋为例,游戏大厅中有N个房间,那么每个房间会有一个棋局对象,和X个(将、相、士、炮)棋子对象。以下是go的具体实现flyweight.go

package design_mode

type ChessPiece struct {
	Name      string
	Color     string
	PositionX int
	PositionY int
}

type ChessBoard struct {
	Cards map[int]*ChessPiece
}

func NewChessBoard() *ChessBoard {
	return &ChessBoard{
		Cards: map[int]*ChessPiece{
			1: {
				Name:      "車",
				Color:     "紅",
				PositionX: 1,
				PositionY: 11,
			},
			2: {
				Name:      "馬",
				Color:     "黑",
				PositionX: 2,
				PositionY: 2,
			},
		},
		// 其他棋子
	}
}


开N个房间flyweight_test.go

package design_mode

import (
	"testing"
)

func TestNewChessBoard(t *testing.T) {
	game1 := NewChessBoard()
	game2 := NewChessBoard()
	t.Log(game1.Cards[1])
	t.Log(game2.Cards[1])
	t.Log(game1.Cards[1] == game2.Cards[1])
}

OutPut:

=== RUN   TestNewChessBoard
    flyweight_test.go:15: &{車 紅 1 11}
    flyweight_test.go:16: &{車 紅 1 11}
    flyweight_test.go:17: false
--- PASS: TestNewChessBoard (0.00s)

例中,每创建一个棋局就需要初始化对应的棋子,如果游戏大厅有百万人同时在线 ,那保存这么多棋局对象就会消耗大量的内存。

使用享元模式重构

如何对上例进行优化呢,从例子中,每个大厅存在大量重复对象棋牌,利用享元模式把棋牌设计成共享的(map)详细如下。

package design_mode

import "fmt"

var chessPieceUnit = map[int]*ChessPiece{
	1: {
		Name:  "車",
		Color: "紅",
		PositionX: 1,
		PositionY: 11,
	},
	2: {
		Name:  "馬",
		Color: "黑",
		PositionX: 2,
		PositionY: 2,
	},
	// 其他棋子
}

func NewChessPieceUnitFactory() *ChessBoard {
	board := &ChessBoard{Cards: map[int]*ChessPiece{}}
	for id := range chessPieceUnit {
		board.Cards[id] = chessPieceUnit[id]
	}
	return board
}

output:

=== RUN   TestNewChessPieceUnitFactory
    flyweight2_test.go:13: &{  1 11}
    flyweight2_test.go:14: &{  1 11}
    flyweight2_test.go:15: true
--- PASS: TestNewChessPieceUnitFactory (0.00s)

例中,通过一个ChessPieceUnit来缓存一个ChessBoard棋局的30+个ChessPiece棋子对象。原本如果是1万个ChessBoard棋局,需要创建【1万乘30】约30万个棋子对象。现在通过共享同一个ChessPieceUnit,那么需要创建约【30】个对象,大大节省了内存。

参考

jefffff

Stay hungry. Stay Foolish COOL

Go backend developer

China Amoy