前记

本地 Windows,在做 k8s kubebuilder 开发的时候是在本机安装一台 Cnetos 虚拟机来作为开发环境,这个时候就需要用到远程开发了

安装

1、Vscode 上安装 Remote - SSH 插件

2、设置配置文件

1
2
3
4
5
6
7
8
# 配置文件路径
# C:\Users\jakehu\.ssh\config

# 配置文件格式
Host kubebuilder
HostName 10.113.73.205
User root
Port 22

3、配置免密登陆

将本地公钥 C:\Users\jakehu\.ssh\id_rsa.pub 放入服务器 ~/.ssh/authorized_keys 即可


Remote 参考

前记

虚拟机是 Centos,利用桥接模式进行网络连接,输入 ip a s 命令 ens33 网卡没有 IP 地址

解决

修改配置文件:

1
vi /etc/sysconfig/network-scripts/ifcfg-ens33

修改如下配置:

1
ONBOOT=yes

重启:

1
reboot

题外话:

安装 VMware Tools

前记

昨天在 Windows 上成功安装了 kubebuilder 但是在最后 make install 环节一直过不去,想想算了,还是用 Centos

依赖

这是官方给出的需求依赖

1
2
3
4
go version v1.17.9+
docker version 17.03+.
kubectl version v1.11.3+.
Access to a Kubernetes v1.11.3+ cluster.

GO

1
2
3
4
wget https://go.dev/dl/go1.18.4.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.18.4.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go version

Docker

Centos Docker 安装文档

1
2
# 自启动Docker
systemctl enable docker.service

K8S

Minikube 安装

问题 1:X Exiting due to DRV_AS_ROOT: The "docker" driver should not be used with root privileges.

1
minikube start --force --driver=docker

kubebuilder

kubebuilder 安装


到此 kubebuilder 环境就算搭建完成

Cygwin

安装

Cygwin 就是一个 Windows 软件,该软件就是在 Windows 上仿真 Linux 操作系统

基础模块:BaseDevelLibsNetSystemUtils

这里我们安装 BaseDevel 就够了

Cygwin 包可以通过 UI 界面进行安装,也可以通过将下载来下来的 exe 文件放在 C:\cygwin64\bin 目录下

Cygwin 镜像

包管理

除了上面说的,我们还可以使用第三发的包管理工具 apt-cyg

安装:

1
2
3
curl -O https://raw.githubusercontent.com/transcode-open/apt-cyg/master/apt-cyg
mv apt-cyg /bin/apt-cyg
chmod +x /bin/apt-cyg

使用:

1
apt-cyg install wget

Kubebuilder

依赖

1
2
3
4
go version v1.17.9+
docker version 17.03+.
kubectl version v1.11.3+.
Access to a Kubernetes v1.11.3+ cluster.

还需要安装 Make

1
apt-cyg install make

安装

1
2
3
git clone git@github.com:kubernetes-sigs/kubebuilder.git
cd kubebuilder
make install

位置

1
2
which kubebuilder
/cygdrive/c/Users/jakehu/go/bin/kubebuilder

使用

1
2
go mod init jakehu.me
kubebuilder init --domain jakehu.me

目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
tree
.
├── Dockerfile
├── Makefile
├── PROJECT
├── README.md
├── config
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager
│   │   ├── controller_manager_config.yaml
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   └── rbac
│   ├── auth_proxy_client_clusterrole.yaml
│   ├── auth_proxy_role.yaml
│   ├── auth_proxy_role_binding.yaml
│   ├── auth_proxy_service.yaml
│   ├── kustomization.yaml
│   ├── leader_election_role.yaml
│   ├── leader_election_role_binding.yaml
│   ├── role_binding.yaml
│   └── service_account.yaml
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
└── main.go

6 directories, 25 files

kubebuilder 文档

题外话:make 下载失败的时候请走代理

1
2
export http_proxy="127.0.0.1:10809"
export https_proxy="127.0.0.1:10809"

需求

需要构建的 SQL 条件如下:

1
where field1 in (1,2) and field2 not in (1,3) and (field3 like "%test%" or field4 like "%test%")

非动态

如果是非动态其实还是比较好实现的

1
2
where = Q(field1__in=(1,2)) & ~Q(field2__in=(1,3)) & (Q(field3__icontains="test") | Q(field4__icontains="test"))
MyObject.objects.filter(where)

动态实现

1
2
3
4
5
6
7
8
9
10
Q1 = Q()
if request.query_params.get("group_id") is not None:
Q1.add(Q(cid_id__in=[1,2]), Q.AND) # 1,2 is the group_id
Q1.add(~Q(id__in=[1,2]), Q.AND)
Q2 = Q()
if request.query_params.get("name") is not None:
Q2.add(Q(schema__icontains=request.query_params.get("name")), Q.OR)
Q2.add(Q(cid__host__icontains=request.query_params.get("name")), Q.OR)
Q2.add(Q(cid__env__name__icontains=request.query_params.get("name")), Q.OR)
MyObject.objects.filter(Q1 & Q2)

上面实现中 cid 为外键 cid_id 为外键表 id 字段,cid__env__namecid 外键表的外键表 env 的字段 name

前记

最近在用 Go 做一个小程序,需要生成一个 6 位数随机验证码,遂记录一下实现

实现

Go 生成特定长度的随机数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"math/rand"
"strings"
"time"
)

func main() {
fmt.Println(RandomNumber(6))
}

// width 表示需要的位数
func RandomNumber(width int) string {
numeric := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
r := len(numeric)
rand.Seed(time.Now().UnixNano())

var sb strings.Builder
for i := 0; i < width; i++ {
fmt.Fprintf(&sb, "%d", numeric[rand.Intn(r)])
}
return sb.String()
}

Colima 也已经大半年了,作为 Docker Desktop 的替代者用起来还是觉得不错的

目前最新版 0.4.2

项目地址

安装

这里只以 Homebrew 为例

1
brew install colima

大概有如下依赖

1
dependencies for colima: gdbm, ca-certificates, openssl@1.1, sqlite, python@3.9, glib, libtool, guile, nettle, libnghttp2, unbound, gnutls, libslirp, libusb, qemu and lima

更新

删除现有的 VM 实例

1
colima delete

Homebrew 更新

1
2
brew update
brew upgrade colima

start

1
colima start

使用

使用这块可以参考官方文档,底层的 runtime 默认是 Docker

kubernetes 为例,需要先安装 kubectl

1
brew install kubectl

然后再启动 colima

1
2
3
4
5
6
7
8
9
10
11
colima start --with-kubernetes

INFO[0000] starting colima
INFO[0000] runtime: docker+k3s
INFO[0000] preparing network ... context=vm
INFO[0000] starting ... context=vm
INFO[0030] provisioning ... context=docker
INFO[0030] starting ... context=docker
INFO[0035] provisioning ... context=kubernetes
INFO[0036] starting ... context=kubernetes
INFO[0040] done

查看状态

1
2
3
4
5
6
7
8
colima status

INFO[0000] colima is running
INFO[0000] arch: x86_64
INFO[0000] runtime: docker
INFO[0000] mountType: sshfs
INFO[0000] socket: unix:///Users/jakehu/.colima/default/docker.sock
INFO[0000] kubernetes: enabled

前记

最近在用 gin 开发一款小程序后端需要用到小程序登陆授权,看看如何实现

小程序登陆流程图

核心代码

核心代码实现,这里主要是利用了 silenceper/wechat

InitWechat 函数主要是初始化 Wechat 同时去设置缓存

MiniProgramLogin 函数主要是去调用 Code2Session 进行授权,同时将 code 换成 session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package utils

import (
"github.com/gin-gonic/gin"
"github.com/silenceper/wechat/v2"
"github.com/silenceper/wechat/v2/cache"
miniConfig "github.com/silenceper/wechat/v2/miniprogram/config"
)

func InitWechat(c *gin.Context) *wechat.Wechat {
wc := wechat.NewWechat()
redisOpts := &cache.RedisOpts{
Host: "127.0.0.1:3306",
Database: 0, // redis db
MaxActive: 10, // 连接池最大活跃连接数
MaxIdle: 10, //连接池最大空闲连接数
IdleTimeout: 60, //空闲连接超时时间,单位:second

}
redisCache := cache.NewRedis(c, redisOpts)
wc.SetCache(redisCache)
return wc
}

func MiniProgramLogin(c *gin.Context, code string) (map[string]interface{}, error) {
wc := InitWechat(c)
cfg := &miniConfig.Config{
AppID: "APPID",
AppSecret: "APPSECRET",
}
miniprogram := wc.GetMiniProgram(cfg)
auth := miniprogram.GetAuth()
session, err := auth.Code2Session(code)

result := map[string]interface{}{}
if err != nil {
return result, err
}
result["openid"] = session.OpenID
result["session_key"] = session.SessionKey
result["unionid"] = session.UnionID
return result, nil
}

小程序

小程序需要调用 wx.login 获取到 code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
App({
globalData: {},
onLaunch: function () {
var token = wx.getStorageSync('token'); // 获取本地缓存
if (!token) {
wx.login({
success: function (_a) {
var code = _a.code; // 获取到登陆code
if (code) {
console.log(code);
// 在这里请求API进行登陆
}
}
});
return;
}
}
});

调用

1
utils.MiniProgramLogin(c, code)

cgin 上下文对象 *gin.Context

code 为前端传入小程序 wx.login 获取到的 code


纵有千古,横有八荒;前途似海,来日方长。

单次构造

这种方式打包环境和运行环境都在同一个 Golang 底层镜像中

1
2
3
4
5
6
7
8
9
FROM golang:1.17-alpine
WORKDIR /build # 工作目录
ADD . ./ # 复制文件到工作目录
ENV GO111MODULE=on \ # 设置Go Module模式,并修改代理
GOPROXY=https://goproxy.cn
RUN go mod download # 下载go.mod里面所用到的包
RUN go build -o hejiusha . # 编译
EXPOSE 8888 # 暴露端口
ENTRYPOINT ["./hejiusha"] # 运行

多段构造

这种方式可以把编译环境和运行环境进行分割,极大的减小了运行镜像的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#----------build----------#
FROM golang:1.17-alpine AS builder
WORKDIR /build # 工作目录
ADD . ./ # 复制文件到工作目录
ENV GO111MODULE=on \ # 设置Go Module模式,并修改代理
GOPROXY=https://goproxy.cn
RUN go mod download # 下载go.mod里面所用到的包
RUN go build -o hejiusha . # 编译

#----------run----------#
FROM alpine:latest # 运行环境换为alpine
WORKDIR /build/ # 采用相同的目录作为工作目录
COPY --from=builder /build . # 把golang镜像中编译好的二进制文件复制到运行镜像中
EXPOSE 8888 # 暴露端口
ENTRYPOINT ["./hejiusha"] # 运行

俗世洪流,站得住脚已是千辛万苦,想要出人头地,恐怕比登天还难。

前记

最近在做监控 Spring Boot /actuator/health 的时候,总是会出现一些莫名其妙的网络超时中断,于是想到了用重试机制来进行重试请求。

下面看看 Python 的第三方库 Tenacity

安装

1
pip install Tenacity

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
from tenacity import retry, stop_after_attempt, wait_fixed

# reraise 表示是否抛出原异常,默认为 False
# stop_after_attempt 表示最大尝试次数
# wait_fixed 表示等待时间
@retry(reraise=True, stop=stop_after_attempt(2), wait=wait_fixed(2))
def main():
print("Start Try " + str(main.retry.statistics["attempt_number"]))
resp = requests.get("https://httpstat.us/200?sleep=5000", timeout=3)
print(resp.status_code)

try:
main()
except Exception as e:
print(str(e))

输出:

1
2
3
Start Try 1
Start Try 2
HTTPSConnectionPool(host='httpstat.us', port=443): Read timed out. (read timeout=3)

上面示例中,设定超时为 3s 请求却 sleep=5000 此请求必然会超时

tenacity 只要遇见 raise 就会触发重试,上面代码中 requests 底层已经 raise,所以即使 main 函数中没有 raise 依然会重试

参考文档