0%

配置环境

  1. VSCode,安装 Flutter Intf 插件

image-20220218123823301

  1. 打开工程,初始化模块

image-20220218123930926

初始化后,会在lib目录中看到

1
2
3
4
5
6
lib
- generated
- intl
- l10n.dart
- l10n
- intl_en.arb

根据需要,通过 >Flutter Intl: Add locale 添加需要支持的语言

image-20220218124131700

添加后,会在 l10n 目录下看到 intl_xx_xx.arb 的资源文件,需要使用的文案往里面添加即可。

使用资源

在初始化MaterialApp的代码中,添加国际化的支持

1
2
3
4
5
6
7
8
9
10
11
return MaterialApp(
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
locale: model.locale, // Locale("en", "US"), Locale("zh", "CN") 等等
home: const MyHomePage(),
);

在后面的Widget中,可以通过context来获取对应的文案

1
S.of(context).xxx 

前置知识

Kubernetes部署Gitea+Drone+Registry

因为作者的Gitea是部署在k8s上,所以这次配置的 govanityurls 也是在k8s上构建,前置的环境不做多说。

构建govanityurls镜像

源代码:https://github.com/GoogleCloudPlatform/govanityurls

拉取源代码后,执行 go build 即可。

然后配置 Dockerfile 构建镜像,推送的自己的代码仓库中。

1
2
3
4
5
FROM alpine:3.13.5
WORKDIR /app
COPY ./govanityurls .
EXPOSE 8080
CMD /app/govanityurls

如果不想自己构建,可以直接使用其他人构建好的,例如下面的

1
garukun/govanityurls:latest

在k8s配置 govanityurls

创建 deployment.yaml

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: govanityurls
spec:
replicas: 1
selector:
matchLabels:
app: govanityurls
template:
metadata:
labels:
app: govanityurls
spec:
imagePullSecrets:
- name: regcred
containers:
- name: govanityurls
image: code-registry.domain/govanityurls:v0.1
# 因为我的镜像程序是在 /app/govanityurls , 配置挂载在 /app/config/vanity.yaml
command: [ "/app/govanityurls", "/app/config/vanity.yaml" ]
ports:
- containerPort: 8080
name: http
volumeMounts:
- name: static-pvc
mountPath: "/app/config"
subPath: "govanityurls"
restartPolicy: "Always"
volumes:
- name: static-pvc
persistentVolumeClaim:
claimName: static-pvc

创建 ingress.yaml

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
apiVersion: v1
kind: Service
metadata:
name: govanityurls
labels:
app: govanityurls
spec:
selector:
app: govanityurls
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: govanityurls-ingress
spec:
rules:
- host: pkg.domain
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: govanityurls
port:
number: 8080

配置包路径 vanity.yaml

1
2
3
4
5
6
host: pkg.domain

paths:
/libcomm:
repo: https://gitcode.domain/user/libcomm
vcs: git

参数解说

  1. host,包的域名。例如创建的一个libcomm的包,go引用的路径就是 pkg.domain/libcomm
  2. paths中的repo,git的仓库路径,可以是 https:

例如在gitea中,使用user,创建一个libcomm的仓库,git的https连接为

1
https://gitcode.domain/user/libcomm.git

测试go包

创建仓库 libcomm

1
go mod init pkg.domain/libcomm

编写包代码

1
2
3
package libcomm

const TEST = 1

将代码提交到仓库中。

创建新的项目,测试引用包

1
2
3
4
5
6
7
8
9
package main
import (
"fmt"
"pkg.domain/libcomm"
)

func main() {
fmt.Println(libcomm.TEST)
}

通过 go get 拉取包

1
GIT_TERMINAL_PROMPT=1 go get -v pkg.domain/libcomm

注意:首次必须要追加 GIT_TERMINAL_PROMPT=1,因为私有代码仓库有用户密码验证,不添加这参数,会出现用户密码错误。

如果服务器不支持https ,可以追加 -insecure 参数。

1
go get -v -insecure pkg.domain/libcomm

最后,go build ,一切正常~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# updated: Mar 4 2022
# 上面 go get -insecure 方式已经废弃,会有报错信息
# fatal: could not read Username for xxx terminal prompts disabled
# 解决方法
# 编辑 .gitconfig 替换https访问方式,改成git方式访问私有库
# 将git的 https://git.host 访问地址改成 ssh访问方式
# 第一步
[url "ssh://[email protected]<git.host>:"]
insteadOf = https://git.host
# 第二步
# 配置客户端ssh认证 ~/.ssh/config
# 第三步
# 将仓库标记为私有库
go env -w GOPRIVATE=pkg.domain/libcomm
# 解决,然后直接运行 go get pkg.domain/libcomm ~

参考资料
https://tonybai.com/2017/06/30/go-get-go-packages-in-private-code-repo-by-govanityurls/

https://github.com/GoogleCloudPlatform/govanityurls

https://erwinvaneyk.nl/private-repositories-with-go-mod/

使用CRIO时,POD创建的时候会出现失败,错误信息

Failed to create pod sandbox: rpc error: code = Unknown desc = cri-o configured with systemd cgroup manager, ...

是因为默认情况,CRIO使用的是 cgroupfs(bug),这里需要手动配置一下。

编辑 /var/lib/kubelet/kubeadm-flags.env 追加 –cgroup-driver=systemd 参数

然后重新启动

1
2
systemctl daemon-reload
systemctl restart kubelet

参考

https://github.com/cri-o/cri-o/issues/896

Docker-compose 配置 rabbitmq-management

带管理面板的rabbitmq,直接加载镜像,管理面板的http端口是 15672,直接拉取镜像就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3'

services:
rabbitmq:
image: rabbitmq:3.8.14-management-alpine
container_name: rabbitmq
ports:
- 8080:15672
volumes:
- rabbitmq-data:/var/lib/rabbitmq

volumes:
rabbitmq-data:

然后打开 http://localhost:8080/ 初始化用户密码是 guest/guest 。

配置 rabbitmq-management with ssl

  1. 生成证书

直接使用脚本,比较方便 https://github.com/Berico-Technologies/CMF-AMQP-Configuration

1
2
3
4
5
6
7
8
git clone https://github.com/Berico-Technologies/CMF-AMQP-Configuration
cd CMF-AMQP-Configuration/ssl
# MyRabbitMQCA 是自定义CA的名称
sh setup_ca.sh MyRabbitMQCA
# rabbitmq-server 服务器hostname rabbit 是密码
sh make_server_cert.sh rabbitmq rabbit
# rabbit-client 客户端标识 rabbit 是密码
sh create_client_cert.sh rabbit-client rabbit

注意

1)服务器证书的CN需要与hostname一致,不然在创建tls连接时会失败。

2)这个脚本直接创建,通过golang 创建tls连接时,会出现

failed to connect rabbitmq!:x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0

这个错误。处理方式有两种

A. 修正证书的SAN

B. 通过GODEBUG设置x509ignoreCN=0 忽略

B的方式虽然更简单,但是这种绕过的方式必然不是解决方案。

那怎么修正 SAN 呢,参考下面的步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
# 进入 CMF-AMQP-Configuration/ssl 编辑 openssl.conf
# 找到 [ req ] 在末尾添加
req_extensions = v3_req
# 然后在末尾添加 标签 [ v3_req ] [ alt_names ]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = localhost
DNS.2 = rabbitmq
# 这里的DNS就是证书对应的域名,按需要来添加,目前例子中是 rabbitmq,就这么填。这里支持通配符 *,可以做多域名支持

修改 make_server_cert.sh 脚本

1
2
3
4
5
6
7
8
9
# 找到创建证书的地方
openssl req -new -key $sname.key.pem -out $sname.req.pem -outform PEM -subj /CN=$sname/O=server/ -nodes

openssl ca -in ../server/$sname.req.pem -out ../server/$sname.cert.pem -notext -batch -extensions server_ca_extensions

# 追加 -extensions v3_req
openssl req -new -key $sname.key.pem -out $sname.req.pem -outform PEM -subj /CN=$sname/O=server/ -nodes -extensions v3_req

openssl ca -in ../server/$sname.req.pem -out ../server/$sname.cert.pem -notext -batch -extensions server_ca_extensions -extensions v3_req

修改客户端证书,同样

1
2
3
4
5
6
7
# 找到下面两行
openssl req -new -key $cname.key.pem -out $cname.req.pem -outform PEM -subj /CN=$cname/O=client/ -nodes
openssl ca -in ../client/$cname.req.pem -out ../client/$cname.cert.pem -notext -batch -extensions client_ca_extensions

# 追加 -extensions v3_req
openssl req -new -key $cname.key.pem -out $cname.req.pem -outform PEM -subj /CN=$cname/O=client/ -nodes -extensions v3_req
openssl ca -in ../client/$cname.req.pem -out ../client/$cname.cert.pem -notext -batch -extensions client_ca_extensions -extensions v3_req

然后重新生成一遍证书

执行脚本后,复制证书文件,只需要5个。

cacert.pem

rabbitmq.cert.pem 服务端证书

rabbitmq.key.pem 服务端拿证书

rabbitmq-client.cert.pem 客户端证书

rabbitmq-client.key.pem 客户端证书

将文件复制到 cert 目录中,docker-compose 目录结构如下

1
2
3
4
5
6
7
8
9
rabbitmq-folder
|-- docker-compose.yaml
|-- rabbitmq.conf
|-- cert
|-- cacert.pem
|-- rabbitmq.cert.pem
|-- rabbitmq.key.pem
|-- rabbitmq-client.cert.pem
|-- rabbitmq-client.key.pem

到此为止,证书处理完成。

  1. 配置 rabbitmq.conf
1
2
3
4
5
6
7
8
9
loopback_users.guest = false
listeners.ssl.default = 5671
ssl_options.cacertfile = /cert/cacert.pem
ssl_options.certfile = /cert/rabbitmq.cert.pem
ssl_options.fail_if_no_peer_cert = true
ssl_options.keyfile = /cert/rabbitmq.key.pem
ssl_options.verify = verify_peer

management.tcp.port = 15672
  1. 配置compose
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3'

services:
rabbitmq:
image: rabbitmq:3.8.14-management-alpine
container_name: rabbitmq
hostname: rabbitmq
ports:
- 8080:15672
volumes:
- rabbitmq-data:/var/lib/rabbitmq
- ./rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
- ./cert:/cert

volumes:
rabbitmq-data:

然后唤起验证

打开浏览器

image-20210330041852295

看到 amqp/ssl 5671 , 配置正常。

后语

在 rabbitmq 的镜像中,可以通过 environment 环境变量,设置

RABBITMQ_MANAGER_SSL_CACERTFILE
RABBITMQ_MANAGEMENT_SSL_CERTFILE
RABBITMQ_MANAGEMENT_SSL_KEYFILE

参考:https://hub.docker.com/_/rabbitmq

来配置证书,但是个人并不喜欢直接这么配置,因为直接使用这个,默认情况management plugin 也会开启了ssl。对于自签证书来说,并不方便。

个人更偏好 management plugin 是 http协议的,外层 nginx 统一处理 ssl 证书。

加上本身rabbitmq的配置文件也是需要自定义的,直接在配置文件修改就行,没必要通过环境变量来设置。


相关资料

rabbitmq镜像 https://hub.docker.com/_/rabbitmq

golang 自签证书双向认证 https://studygolang.com/articles/31646

问题背景

在Cluster中,我需要将一个service通过nodePort方式暴露给外部使用。但是,又需要限制只允许白名单的ip访问。

不论是 nginx-ingress,ambassador,好像都没找到合适的方式能直接实现。

于是,只能通过外层的proxy中来控制tcp的流量转发及访问控制了。

Nginx TCP 转发

一般情况,我们是使用

1
2
3
4
5
6
stream {
server {
listen port;
proxy_pass host:port;
}
}

然后,通过这种方式,接收方是无法获取客户请求的真实ip,需要透明代理的方式

1
2
3
4
5
6
7
stream {
server {
listen port;
proxy_pass host:port;
proxy_bind $remote_addr transparent;
}
}

但是问题是,直接这么配置,连接是不通的。还需要手动添加路由表才行。这样看来并不方便。

那么,还有什么方法呢?

Nginx proxy_protocol

查询资料,发现可以通过proxy_protocol的方式来获取到客户的真实ip

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
# proxy server 
stream {
server {
listen port;
proxy_pass internal_proxy:port;
proxy_protocol on;
}
}

# internal_proxy
stream {
upstream svc1 {
server host:port;
}
upstream svc2 {
server host:port;
}
map $remote_addr $backend_svr {
1.1.1.1 "svc1";
default "svc2";
}
server {
listen port proxy_protocol;
set_real_ip_from 0.0.0.0/0;
proxy_pass $backend_srv;
}
}

server接收到请求,通过proxy_protocol的方式,转发到内部的proxy。

内部proxy通过 map $remote_addr 方式,可以指定ip转发流量,达到需要的目的。

尝试解决实际的问题

需求:Cluster内部有个redis服务。需要对外网指定ip暴露访问。

  1. 建立redis服务的代理服务,redis-proxy (通过nginx)

    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
    # Deployment的yaml就不写了,重点是nginx的配置
    # nginx.conf
    http {
    ...
    # 定义 default-svc 的应答
    server {
    listen 80;
    location / {
    return 200 "ok.";
    }
    }
    }
    stream {
    upstream redis-svc {
    server redis-svc:6379;
    }
    upstream default-svc {
    server 127.0.0.1:80;
    }
    map $remote_addr $backend_svr {
    x.x.x.x "redis-svc"; # xxxx是放开的ip,仅这个ip能通过公网访问redis
    default "default-svc"; # 其余的走默认处理
    }
    server {
    listen port proxy_protocol;
    set_real_ip_from 0.0.0.0/0;
    proxy_pass $backend_srv;
    }
    }
  2. 将redis-proxy通过nodePort方式暴露

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    apiVersion: v1
    kind: Service
    metadata:
    name: redis-proxy
    spec:
    type: NodePort
    externalTrafficPolicy: Local
    ports:
    - port: 6379
    targetPort: 6379
    protocol: TCP
    nodePort: 30001
    selector:
    app: redis-proxy
  3. server的nginx代理配置

    1
    2
    3
    4
    5
    6
    7
    8
    # nginx.conf
    stream {
    server {
    listen 6379;
    proxy_pass 127.0.0.1:30001; # nodePort的端口
    proxy_protocol on;
    }
    }
  4. 通过redis-cli进行测试 (redis是带密码的)

    1
    2
    3
    # redis-cli -h x.x.x.x -p 6379
    x.x.x.x:6379> AUTH 1
    (error) ERR invalid password

    在允许访问的机器上访问,能看到正常应答

    1
    2
    3
    # redis-cli -h x.x.x.x -p 6379
    x.x.x.x:6379> AUTH 1
    Error: Protocol error, got "H" as reply type byte

    在其他机器上请求,会看到协议错误,因为设置中是重定向到一个http的server中。

    1
    2
    # curl http://x.x.x.x:6379
    ok.%

    通过curl直接http访问,有正常的应答,符合预期,问题解决。

结语

实际上这种方式也非常不灵活,然而在 nginx-ingress/ambassador中,好像也看不到能简单实现满足这个需求的方式。http/https 可以轻易实现达到,但如果是tcp的流量,好像并没有方便的方式。有待学习研究。