Category Archive : K8S相关

自定义一个kaniko镜像

背景

kaniko是一款方便我们从K8S内部构建docker容器的工具,以前我们在CI过程中,使用的是docker-in-docker技术,这种技术最主要的缺陷就是当一台机器上同时运行多个docker build流水线时,会出现阻塞的情况,因为这一批流水线用的是宿主机上的同一个docker进程。
基于这种情况,我们在droneCI流水线中换用了kaniko来进行docker镜像的创建。

遇到的难题

  1. kaniko是基于scratch构建的,里面没有shell,所以想在kaniko原生镜像里在调用python是很麻烦的
  2. kaniko创建docker镜像使用的是file system功能,如果想在一个kaniko容器里先创建ubuntu镜像,再创建alpine镜像, 是会有各种冲突问题的,需要使用–cleanup功能。此功能会清空文件系统,同时如果有自己装的shell,也会被清空,导致无法再次使用

解决方案

  1. kaniko的关键文件其实是/kaniko目录下的哪些 二进制文件,官方推荐是用gcr.io/kaniko-project/executor 镜像,其实我们可以拷贝这个/kaniko目录到我们自己的私有镜像
  2. shell没有的话,我们可以拷贝一个busybox进去,这样就有shell了
  3. 虽然–cleanup会清空file system,但是根据代码里的ignorepath设定,volume挂载目录和/kaniko目录会被忽略掉。所以我们可以有两种方式选择:一、通过volume的方式哦挂载busybox和自己的python代码到私有镜像里。二、把busybox和python代码加入/kaniko目录。

示例代码

Dockerfile如下:

FROM heiheidoc/kaniko-project-executor:v1.3.0 AS plugin

# 1.6.0的clean up有问题 https://github.com/GoogleContainerTools/kaniko/issues/1586

FROM heiheidoc/kaniko-project-executor:debug AS debug

FROM python:3.9.5-buster

COPY --from=背景plugin /kaniko /kaniko

COPY --from=debug /busybox /kaniko/busybox

ADD . /kaniko

ENV DOCKER_CONFIG /kaniko/.docker

CMD ["python3","/kaniko/main.py"]


部分python代码如下,功能是按照一定规则生成Docker镜像:

def run_shell(shell):
    print_green(shell)
    cmd = subprocess.Popen(shell, stdin=subprocess.PIPE, stderr=sys.stderr, close_fds=True,
                           stdout=sys.stdout, universal_newlines=True, shell=True,executable='/kaniko/busybox/sh', bufsize=1)
    cmd.communicate()
    return cmd.returncode
def run_executor():
    for folder_name, sub_dir, files in os.walk(os.getcwd()):
        if 'Dockerfile' in files:
            Dockefile_path = folder_name + "/Dockerfile"
            docker_info = folder_name.replace(os.getcwd(),'').split('/')
            harbor_image_name = REGISTRY_URL + "/" + owner_name + "/" + docker_info[1] + ":" + docker_info[2]
            cmd_build = "/kaniko/executor --cache=true --cache-dir=/cache --cleanup --skip-tls-verify --skip-tls-verify-pull --skip-tls-verify-registry" \
                        " --dockerfile=" + Dockefile_path + \
                        " --context=dir://" + folder_name + \
                        " --destination=" + harbor_image_name
            assert run_shell(cmd_build) == 0, "镜像build失败: " + harbor_image_name
if __name__ == "__main__":
    run_executor()

K3S环境下接入Prometheus,grafana,等监控套件

背景

因为常规的监控都是用K8S做的,而K3S上的监控方案少之又少,如果直接用rancher上的prometheus监控,会消耗至少2G的内存,于是我们就自己做了K3S和pg数据库的监控,并且通过我们自己做的监控,可以减少一些不必要的性能开销。主要监控容器资源消耗,宿主机资源消耗,pg数据库资源消耗

这个是资源使用情况,大约会用掉600M内存和100MCPU

user@user:/$ kubectl top pod -n kube-ops
NAME CPU(cores) MEMORY(bytes)
grafana-0 1m 47Mi
kube-state-metrics-594fb7bc84-5mg6g 3m 10Mi
node-exportor-prometheus-node-exporter-sznls 31m 8Mi
node-exportor-prometheus-node-exporter-tx7rm 13m 7Mi
prometheus-0 46m 565Mi
prometheus-postgres-exporter-6c858f47d4-dj9hj 13m 6Mi

prometheus监控组件参考helm chart

https://github.com/prometheus-community/helm-charts/tree/main/charts
我魔改的github连接
https://github.com/lizhenwei/k3s-prometheus


Prometheus的安装

1.创建一个命名空间kube-ops,把监控用的东西都放在这个命名里面。
2.使用我魔改的chart进行安装,默认版本是v2.26.0,默认启用上图提到的

cadvisor,kube_state_metrics,node_exportor,postgres_exporter

 

kubectl create ns kube-ops
helm install -n kube-ops prometheus lizhenwei-prometheus

如果要关闭其中的某个监控项,安装时参考添加变量 –set node_exportor.enable=false,
prometheus的数据会用默认的storageClass生成PVC,如果没有默认的,参考手动设置nfs存储 –set persistence.storageClass=nfs-client
安装成功后,因为我是nodeport暴露出来的,所以可以通过浏览器去检查一下配置,例如这里我们暴露出来是32331端口可以访问,例如:

$ kubectl get svc -n kube-ops
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
prometheus NodePort 10.43.178.128 <none> 9090:32331/TCP 2d4h

访问http://master机器IP:32331/targets,可以看到prometheus自己已经是成功起来了,我们也可以去http://master机器IP:32331/config页面看看配置项是否都正确
要启用或停用某些监控功能,可以修改lizhenwei-prometheus/valuse.yaml里的配置,修改成true或false


kube-state-metrics的安装

kube-state-metrics是用来收集k8s集群的CPU,内存等信息的收集器,这里我用的是v1.9.7版本。
使用我github上的helm进行安装:

helm install -n kube-ops prometheus lizhenwei-kube-state-metrics

要想验证部署kube-state-metrics之后,是否能成功使用,我们可以看prometheus的target页面是不是显示kube-state-metrics(X/X up) x为机器节点数量。
然后我们去graph页面看看,http://master机器IP:32331/graph。可以参考官网给的promql文档,输入几个语句试试

PromQL参考链接

https://github.com/kubernetes/kube-state-metrics/tree/master/docs

例如输入kube_configmap_info,点击execute会返回一些结果,如下图


K3S自带的CADVISOR

K3S自带kubelet ,而cadvisor已经集成在kubelet里了,prometheus可以通过配置找到本地安装的cadvisor,例如下列代码:

- job_name: 'cadvisor'
  scheme: https
  tls_config:
    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
  kubernetes_sd_configs:
  - role: node
  relabel_configs:
  - action: labelmap
    regex: __meta_kubernetes_node_label_(.+)
  - target_label: __address__
    replacement: kubernetes.default.svc:443
  - source_labels: [__meta_kubernetes_node_name]
    regex: (.+)
    target_label: __metrics_path__
    replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor

接入prometheus的时候,可以根据以下链接,查看PromQL语句

PromQL参考链接

https://github.com/google/cadvisor/blob/master/docs/storage/prometheus.md


node-exportor的安装

这个使用官方网站给的一个公共库就可以了

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install node-exportor prometheus-community/prometheus-node-exporter

grafana参考链接https://grafana.com/grafana/dashboards/1860


postgres-exportor

下载代码https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus-postgres-exporter 到机器上
修改valuse.yaml里的queries里的参数,在最后面加上

pg_stat_activity:
      query: |
        SELECT
          datname,
          SUM(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - state_change))::bigint)::float AS process_idle_seconds_sum,
          COUNT(*) AS process_idle_seconds_count
        FROM pg_stat_activity
        WHERE state = 'idle'
        GROUP BY datname
      metrics:
        - datname:
            usage: "LABEL"
            description: "datname"
        - process_idle_seconds:
            usage: "COUNTER"
            description: "Idle time of server processes"
        - process_idle_seconds_sum:
            usage: "GUAGE"
            description: "sum of Idle time of server processes"

在valuse.yaml文件的config.datasource部分添加我们的postgres超管用户信息。

helm安装语句

helm install -n kube-ops prometheus-postgres-exporter prometheus-postgres-exporter

Grafana导入dashboard

可以参考这位大神的github链接https://github.com/starsliao/Prometheus


页面截图

 

rancher备份K8S集群数据到minio方案

1 安装minio

使用自定义证书,并且提供https
docker安装,数据和配置挂载到宿主机上

1.1 证书生成命令

# 制作两个目录用于存放minio数据
mkdir -p /home/lizhenwei/minio/data
mkdir -p /home/lizhenwei/minio/config/certs
# 进入minio证书目录
cd  /home/sfere/lizhenwei/config/certs
# 使用openssl产生伪随机字节
openssl rand -writerand .rnd
# 创建私钥
openssl genrsa -out private.key 2048
# 创建证书文件
openssl req -new -x509 -days 3650 -key private.key -out public.crt -subj "/C=CN/ST=NanJing/L=YuHua/O=LZW/CN=192.168.0.237"

完成之后,/home/lizhenwei/minio/config/certs目录下应该会有CAs  private.key  public.crt 三个文件或目录,其中public.crt 是我们后面要填入rancher中的证书文件

1.2 docker运行minio

docker run -d -p 443:443 --name minio-rancher   -e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE"   -e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"   -v /home/lizhenwei/minio/data:/data   -v /home/lizhenwei/minio/config:/root/.minio   minio/minio server --address ":443" /data

2 rancher中配置集群数据备份到minio

2.1.登录minio,创建bucket

2.2 在rancher中设置etcd备份到S3存储(minio)

Docker运行ApacheIoTDB

ApacheIoTDB-Server的部署方式

ApacheIoTDB提供了三种部署方式:源码部署,二进制程序部署,Docker部署
因为我的电脑是Ubuntu的,不兼容ApacheIoTDB,所以我采用了docker方式进行部署(docker是用debian的java镜像)


部署服务端

1.在ubuntu机器上创建目录~/iotdb-0.11.2
2.复制官方网站的dockerfile到本地:https://github.com/apache/iotdb/blob/master/docker/src/main/Dockerfile-0.11.2
3.输入以下命令制作镜像,确认docker 镜像生成成功

docker build -t iotdb:0.11.2 .

4.输入以下命令,运行iotdb,并且把数据目录和日志目录挂载到~/iotdb-0.11.2下

docker run --name iotdb -p 6667:6667 -v /home/boring/iotdb-0.11.2/data:/iotdb/data -v /home/boring/iotdb-0.11.2/logs:/iotdb/logs -d iotdb:0.11.2 /iotdb/bin/start-server.sh

在容器中用命令行工具进行测试

1.进入容器

docker exec -it iotdb bash

2.使用命令行工具连接

/iotdb/sbin/start-cli.sh -h 127.0.0.1 -p 6667 -u root -pw root

3.确认界面出现如下返回

---------------------
Starting IoTDB Cli
---------------------
 _____       _________  ______   ______
|_   _|     |  _   _  ||_   _ `.|_   _ \
  | |   .--.|_/ | | \_|  | | `. \ | |_) |
  | | / .'`\ \  | |      | |  | | |  __'.
 _| |_| \__. | _| |_    _| |_.' /_| |__) |
|_____|'.__.' |_____|  |______.'|_______/  version 0.11.2
IoTDB> login successfully
IoTDB>

解决helm部署报错Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress

在使用helm install 或者helm upgrade的时候,如果出现了异常中断操作, 可能会导致如下报错

Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress

那么问题来了,如何解决这个问题呢?
参考github上的issues:https://github.com/helm/helm/issues/8987,我们可以使用以下操作
1.输入helm history 命令检查当前状态, 如下

$ helm history -n lizhewnei lizhewnei-common
REVISION	UPDATED                 	STATUS         	CHART                 	APP VERSION	DESCRIPTION
331     	Tue Feb 23 23:11:07 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
332     	Wed Feb 24 08:11:08 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
333     	Wed Feb 24 15:11:13 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
334     	Wed Feb 24 23:11:09 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
335     	Thu Feb 25 08:11:09 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
336     	Thu Feb 25 15:11:08 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
337     	Thu Feb 25 23:11:06 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
338     	Fri Feb 26 08:11:13 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
339     	Fri Feb 26 09:49:37 2021	deployed       	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
340     	Fri Feb 26 10:37:53 2021	pending-upgrade	lizhewnei-common-0.1.0	1.16.0     	Preparing upgrade

2.根据上述状态,我们会发现,最近的一次340部署结果是pending-upgrade 所以阻塞了我们的继续部署
3.我们使用helm rollback命令回退一个版本到339版本

$ helm rollback -n lizhewnei lizhewnei-common 339
Rollback was a success! Happy Helming!

4.回退之后,再检查一次当前状态,确认状态信息是回退到339版本

$ helm history -n lizhewnei lizhewnei-common
REVISION	UPDATED                 	STATUS         	CHART                 	APP VERSION	DESCRIPTION
332     	Wed Feb 24 08:11:08 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
333     	Wed Feb 24 15:11:13 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
334     	Wed Feb 24 23:11:09 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
335     	Thu Feb 25 08:11:09 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
336     	Thu Feb 25 15:11:08 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
337     	Thu Feb 25 23:11:06 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
338     	Fri Feb 26 08:11:13 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
339     	Fri Feb 26 09:49:37 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
340     	Fri Feb 26 10:37:53 2021	pending-upgrade	lizhewnei-common-0.1.0	1.16.0     	Preparing upgrade
341     	Fri Feb 26 11:00:23 2021	deployed       	lizhewnei-common-0.1.0	1.16.0     	Rollback to 339

5.这个时候,我们再去使用helm upgrade命令,就可以正常的升级了,升级之后,我们通过helm history 也可以检查到升级成功,

$ helm history -n lizhewnei lizhewnei-common
REVISION	UPDATED                 	STATUS         	CHART                 	APP VERSION	DESCRIPTION
333     	Wed Feb 24 15:11:13 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
334     	Wed Feb 24 23:11:09 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
335     	Thu Feb 25 08:11:09 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
336     	Thu Feb 25 15:11:08 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
337     	Thu Feb 25 23:11:06 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
338     	Fri Feb 26 08:11:13 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
339     	Fri Feb 26 09:49:37 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
340     	Fri Feb 26 10:37:53 2021	pending-upgrade	lizhewnei-common-0.1.0	1.16.0     	Preparing upgrade
341     	Fri Feb 26 11:00:23 2021	superseded     	lizhewnei-common-0.1.0	1.16.0     	Rollback to 339
342     	Fri Feb 26 11:01:27 2021	deployed       	lizhewnei-common-0.1.0	1.16.0     	Upgrade complete
 
 
 

基于istio的灰度发布实验

背景

灰度发布又叫A/B测试,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。
因为最近刚好有灰度发布的需求,我又学了一遍istio,记录了本次灰度发布的实施过程(只包括应用,不包括数据库升级)


实验过程

  1. 先确定目前的应用版本为V1
  2. 通过helm包部署应用版本为V2的pod到K8S集群中
  3. 确定V2版本灰度的用户,方法包括IP,或者特定用户
  4. 通过istio的virtualservice功能把特定用户的流量指向V2版本
  5. 检查特定用户使用一段时间后,是否出现问题
  6. 若无问题,通过istio将所有用户的流量都指向V2版本
  7. 若所有用户都使用V2无问题,删除掉V1版本的pod

示例介绍

前端应用frontend,后端应用mqtt-server,后端应用mqtt-server 通过mqtt协议与设备相连接。
前端部署3个版本,分别是V1,V2,V3,后端同样部署3个版本,也是V1,V2,V3。3个前端版本,按钮文字不一样。3个后端版本,连接的mqtt设备不一样

版本 前端页面 后端返回参数
V1 显示V11按钮
{"message":["wsytest010","wsytest002",
"wsytest003","wsytest007","wsytest006",
"wsytest001","wsytest005","wsytest009",
"wsytest008","wsytest004"]}
V2 显示V22按钮
{"message":["wsytest019","wsytest020",
"wsytest017","wsytest012","wsytest011",
"wsytest014","wsytest018","wsytest015",
"wsytest013","wsytest016"]}
V3 显示V33按钮
{"message":["wsytest024","wsytest028",
"wsytest022","wsytest026","wsytest027",
"wsytest021","wsytest025","wsytest030",
"wsytest023","wsytest029"]}


根据需求,版本不能串,比如前端V1->后端V1,不允许出现前端V1→后端V2这样的情况发生
这里我们在选择分配流量方式时,不能使用权重的方式进行分配,只能选择指定用户或者指定IP,如果选择权重的方式,可能会出现如下的问题:
前端会访问多个js,css等文件,如果使用权重的方式,会出现一部分js来源于v1版本,一部分css来源于v2版本。
后端也同理,如果一个页面打开时,触发多个后端请求,部分来源于V2,部分来源于V1,肯定会导致前端显示出现问题。
所以只有把前后端通过某种方式一一对应,才能正常使用


代码实现与注意事项

1.部署前端的3个应用程序,所有的pod都加上 labels:[app:frontend,version:#{对应版本}]

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  labels:
    app: frontend
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
      version: v1
  template:
    metadata:
      labels:
        app: frontend
        version: v1
    spec:
      containers:
      - name: frontend
        image: 前端镜像:v1
        securityContext:
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]    # 按照istio的说明,最好把这个pod安全策略加上
        imagePullPolicy: Always
        ports:
        - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-v2
  labels:
    app: frontend
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
      version: v2
  template:
    metadata:
      labels:
        app: frontend
        version: v2
    spec:
      containers:
      - name: frontend
        image: 前端镜像:v2
        securityContext:
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]
        imagePullPolicy: Always
        ports:
        - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-v3
  labels:
    app: frontend
    version: v3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
      version: v3
  template:
    metadata:
      labels:
        app: frontend
        version: v3
    spec:
      containers:
      - name: frontend
        image: 前端镜像:v3
        securityContext:
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]
        imagePullPolicy: Always
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  selector:
    app: frontend
  type: ClusterIP   #这个不用NodePort,因为流量如果是从NodePort进来的,就控不住的
  ports:
    - port: 80
      targetPort: 80
      name: http-web

2.部署后端应用程序,与前端应用类似

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mqtt-server-v1
  labels:
    app: mqtt-server
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mqtt-server
      version: v1
  template:
    metadata:
      labels:
        app: mqtt-server
        version: v1
    spec:
      serviceAccountName: mqtt-server
      containers:
      - name: mqtt-server
        image: 后端镜像:latest
        securityContext:
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]    # 按照istio的说明,最好把这个pod安全策略加上
        imagePullPolicy: Always
        ports:
        - containerPort: 8000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mqtt-server-v2
  labels:
    app: mqtt-server
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mqtt-server
      version: v2
  template:
    metadata:
      labels:
        app: mqtt-server
        version: v2
    spec:
      serviceAccountName: mqtt-server
      containers:
      - name: mqtt-server
        image: 后端镜像:latest
        securityContext:
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]
        imagePullPolicy: Always
        ports:
        - containerPort: 8000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mqtt-server-v3
  labels:
    app: mqtt-server
    version: v3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mqtt-server
      version: v3
  template:
    metadata:
      labels:
        app: mqtt-server
        version: v3
    spec:
      serviceAccountName: mqtt-server
      containers:
      - name: mqtt-server
        image: 后端镜像:latest
        securityContext:
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]
        imagePullPolicy: Always
        ports:
        - containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
  name: mqtt-server
spec:
  selector:
    app: mqtt-server
  type: NodePort   #这个不用NodePort,因为流量如果是从NodePort进来的,就控不住的
  ports:
    - port: 8000
      targetPort: 8000
      name: http-web

3.区分外部流量和内部流量。我们将浏览器到前端的称为外部流量,K8S里的例如前端到后端的称为内部流量

4.外部流量出去,需要被istio的ingress gateway管控起来,所以需要配置一个gateway

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: bookinfo
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

5.配置后端的virtualservice和destination,确保后端程序能与前端程序产生一对一的关系,在无对应关系时,默认使用V1版本

---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: mqtt-server-internal
spec:
  hosts:
  - "mqtt-server"     #此处是关键,把匹配到该url的流量,全部走到这个特定的virtualservice里
  http:
  - match:
    - sourceLabels:
        version: v1
    route:
    - destination:
        host: mqtt-server
        subset: v1             # 将匹配到的流量,转向subset的v1版本,这个subset: v1在destination.yaml里定义
      headers:
        response:
          add:
            user: v1
  - match:
    - sourceLabels:
        version: v2
    route:
    - destination:
        host: mqtt-server
        subset: v2
      headers:
        response:
          add:
            user: v2
  - match:
    - sourceLabels:
        version: v3
    route:
    - destination:
        host: mqtt-server
        subset: v3
      headers:
        response:
          add:
            user: v3
  - route:
    - destination:
        host: mqtt-server
        subset: v1
      headers:
        response:
          add:
            user: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: mqtt-server
spec:
  host: mqtt-server.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1    # 根据pod的 version: v1 的label来进行匹配
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3

6.配置前端的virtualservice和destination,我们可以设置来源于192.168.0.58这个IP的走V2版本,其余IP走V1版本

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: frontend-server
spec:
  hosts:
  - "外网域名"     #此处是关键,把匹配到该url的流量,全部走到这个特定的virtualservice里
  gateways:
  - bookinfo-gateway              #此处必须对应上gateway的名字
  http:
  - match:
    - headers:
      X-Forwarded-For:
          exact: "192.168.0.58"            #此处表示匹配header里有{"user":"v1"}
    route:
    - destination:
        host: mqtt-server
        subset: v2             # 将匹配到的流量,转向subset的v1版本,这个subset: v1在destination.yaml里定义
      headers:
        response:
          add:
            user: v2
  - route:
    - destination:
        host: frontend
        subset: v1             # 将匹配到的流量,转向subset的v1版本,这个subset: v1在destination.yaml里定义
      headers:
        response:
          add:
            user: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: frontend
spec:
  host: frontend
  subsets:
  - name: v1
    labels:
      version: v1    # 根据pod的 version: v1 的label来进行匹配
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3

7.因为我们的浏览器访问的时候,会经过istio,所以前端收到的IP并不是真是的IP,我们需要修改istio的ingress文件,把spec.externalTrafficPolicy设置成Local,如下图所示

8.最终情况


实验效果图

1.当本机IP地址不符合条件时,前端和后端都是V1版本的结果,第一张图是实际效果,第二张图是kiali显示的流量图
2.当本机IP符合条件时,前端和后端都是V2版本的结果,左图是实际效果,右图是kiali显示的流量图

3.当同时有满足IP和不满足IP条件的机器访问时,流量图效果如下

从零开始安装istio与skywalking

版本

istio 安装1.8.2版本
skywalking安装8.1.0版本
K8S集群使用rancher安装1.19版本


istio安装

1.下载istio包到k8s的任意一台master机器上

curl -L https://istio.io/downloadIstio | sh -

2. 进入istio目录,设置环境变量,后续我们的istio安装,都在该目录下进行操作

cd istio-1.8.2
export PATH=$PWD/bin:$PATH

3.安装istio,同时设置skywalking-oap地址

输入如下命令
istioctl install \
  --set profile=demo \
  --set meshConfig.enableEnvoyAccessLogService=true \
  --set meshConfig.defaultConfig.envoyAccessLogService.address=skywalking-oap.istio-system:11800 等待出现如下回显即可完成 ✔ Istio core installed
✔ Istiod installed
✔ Egress gateways installed
✔ Ingress gateways installed
✔ Installation complete

4.安装kiali。安装成功后,记得把kiali通过ingress暴露出来,我是使用的traefik来暴露的

kubectl apply -f samples/addons
kubectl rollout status deployment/kiali -n istio-system

skywalking安装

git clone https://github.com/apache/skywalking-kubernetes.git
cd skywalking-kubernetes/chart
helm repo add elastic https://helm.elastic.co
helm dep up skywalking
helm install 8.1.0 skywalking -n istio-system \
  --set oap.env.SW_ENVOY_METRIC_ALS_HTTP_ANALYSIS=k8s-mesh \
  --set fullnameOverride=skywalking \
  --set oap.envoy.als.enabled=true \
  --set ui.image.tag=8.1.0 \
  --set oap.image.tag=8.1.0-es6 \
  --set oap.storageType=elasticsearch

上述命令会安装一个skywalking和一个elasticsearch 6.8.6版本
安装完后,暴露skywalking-ui到外部,让用户可以通过页面访问


安装测试程序

通过以下命令进行安装,安装完成后,可以自己设置一下,通过loadalance暴露到外网访问,loadbalance的IP可以通过metallb搞一个

kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml

部署效果

浏览器进入bookinfo测试程序

进入kiali检查出现的流量分布情况

skywalking的界面

EMQX在K8S中的扩缩容测试与雪崩

背景

emqx使用statefulset的方式部署3-5个pod ,设备认证采用redis的方式

测试步骤

测试目的 测试步骤 测试结果
1 搭建emqx环境,pod数量为3
2 确认emqx的负载均衡功能 通过python编写mqtt连接代码,
连接99个client,检查99个client的分布情况
平均分布在3个pod上,每个pod上连接了33个client。
多个pod能实现负载均衡。
3 增加emqx的数量,检查连接是否发生变化 通过rancher,修改pod的数量为5,
检查新pod启动之后,99个client的分布情况
新增的2个pod,不会自动分担之前连接的client,原来的
99个client 还是连接到之前的3个pod上。扩容不会影响现有连接。
4 增加客户端的数量,检查服务端负载情况。
看是否能出现削峰平谷,让新连接的pod多负载
一些连接
通过python编写mqtt连接代码,
增加50个client,检查99+50个client的分布情况
每一个pod的连接数量,平摊了10个。两个新的pod变成了10个连接数
原来的3个pod从33个连接变成了43个连接。
新增的客户端也会平均分配到各个pod上,但是分配的方法也是完全平均的,不会考虑emqx当前已经连接的数量。
没有出现削峰平谷的现象。
5 全部重连150个客户端,检查服务端负载情况。
看是否会全部平均分配到5个pod上
断掉之前的连接。
通过python编写mqtt连接代码,
一次性连接150个client,检查他们的分布情况
每个pod分摊30个连接,扩容之后,重新连接的client能够负载均衡
6 缩小emqx的数量,检查连接情况 通过rancher,修改pod的数量为3 消失的2个pod会导致相连客户端断开连接,剩余的3个pod连接的客户端无影响
7 给客户端加上掉线重连功能,再次缩小emqx的数量 通过python编写mqtt连接代码,加上自动重连功能 ,缩小emqx的数量 所有的客户端都会连接到那个1个pod上,虽然客户端产生了掉线,但是不会影响后续使用

结论

1.我们自己实现的mqtt客户端要有重连机制,如果无重连机制,会在缩容或者重启之后与emqx失去连接
2.只要资源充裕,emqx或者redis重启,不会产生太大的影响,顶多是emqx重启的那几秒钟会丢数据
3.emqx的扩容要提前扩容,不能等出现即将出现故障的时候在扩容,因为扩容之后的数据虽然是均摊了,但是前面的2个emqx的连接数还是没有减少
假设每个emqx能容纳100个client,当达到80个client的时候,我们才进行扩容,虽然后面再连上的是负载均衡了,但是在极端情况下还是有可能出现雪崩,如下图

除非此时断开emqx与外部的连接,等待emqx3个实例全部启动之后,再允许emqx的外部连接。
这样每个节点都到82%,没有雪崩

为k8s里运行的容器配置时区

需求背景

我们经常会用一些诸如emqx,nats等第三方中间件,这些中间件往往默认时区就是UTC时区,这其实也没关系,但是打印出来的日志,就会与我们的上海时区差8小时,为了解决这个问题,我的简单解决办法,就是把服务器的时区通过可读的方式挂载进去。

操作步骤

1、把服务器的时区设置成上海时区。ubuntu18系统时区设置方法如下:

timedatectl set-timezone Asia/Shanghai

2、修改emqx的StatefulSet.yaml 。注意挂载服务器的/etc/localtime到容器中,一定要设置readOnly: true,避免被误修改服务器的时区

# Source: emqx/templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: lzw-emqx
  name: lzw-emqx
spec:
  serviceName: lzw-emqx
  replicas: 1
  updateStrategy:
    type: RollingUpdate
  selector:
    matchLabels:
      app: lzw-emqx
  template:
    metadata:
      labels:
        app: lzw-emqx
    spec:
      containers:
        - image: emqx/emqx:4.2.4-alpine-amd64
          imagePullPolicy: IfNotPresent
          name: lzw-emqx
          envFrom:
            - configMapRef:
                name: lzw-emqx
          volumeMounts:
            - name: lzw-emqx-log
              mountPath: /opt/emqx/log
            - name: sfere-time-zone
              mountPath: /etc/localtime
              readOnly: true
          readinessProbe:
            httpGet:
              path: /status
              port: 8081
            initialDelaySeconds: 15
            periodSeconds: 2
      restartPolicy: Always
      volumes:
        - name: lzw-emqx-log
          emptyDir: {}
        - name: sfere-time-zone
          hostPath:
              path: /etc/localtime

traefik配置用户登录,限制K8S的web服务访问

背景

像Elastic-APM, Traefik-Dashboard等页面,是没有用户登录限制的,如果我们希望给他们加上用户登录限制,我们需要在traeifk里给对应的ingress添加登录用的Middleware,那么该如何添加呢?本文以给K8S部署的traefik dashboard为例进行添加

用户名密码加密

1.假设有如下3个用户名密码
lizhenwei 123
zhenwei.li 456
hello thankyou
2.我们通过htpasswd进行加密

htpasswd -nb lizhenwei 123
lizhenwei:$apr1$0wIJg4EG$RZ7wOIyIdg1R4gj4zAlzq1
htpasswd -nb zhenwei.li 456
zhenwei.li:$apr1$PX8cqECj$5zvC3eB1vhLioyjVjdkkE/
htpasswd -nb hello thankyou
hello:$apr1$4nlPGEqZ$nqz2ojkuxAY4FUEy0Tp3x1

3.将加密的信息放入一个叫policy的文件

vi policy
lizhenwei:$apr1$0wIJg4EG$RZ7wOIyIdg1R4gj4zAlzq1 zhenwei.li:$apr1$PX8cqECj$5zvC3eB1vhLioyjVjdkkE/ hello:$apr1$4nlPGEqZ$nqz2ojkuxAY4FUEy0Tp3x1

4.进行base64加密,获得加密后的字符

cat policy | openssl base64 

bGl6aGVud2VpOiRhcHIxJDB3SUpnNEVHJFJaN3dPSXlJZGcxUjRnajR6QWx6cTEK emhlbndlaS5saTokYXByMSRQWDhjcUVDaiQ1enZDM2VCMXZoTGlveWpWamRra0Uv CmhlbGxvOiRhcHIxJDRubFBHRXFaJG5xejJvamt1eEFZNEZVRXkwVHAzeDEK

创建middleware.yaml

将加密后的字符,复制到data.users下面

# Declaring the user list
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: test-auth
spec:
  basicAuth:
    secret: authsecret
---
# Note: in a kubernetes secret the string (e.g. generated by htpasswd) must be base64-encoded first.
# To create an encoded user:password pair, the following command can be used:
# htpasswd -nb user password | openssl base64
apiVersion: v1
kind: Secret
metadata:
  name: authsecret
  namespace: default
data:
  users: |2
    bGl6aGVud2VpOiRhcHIxJDB3SUpnNEVHJFJaN3dPSXlJZGcxUjRnajR6QWx6cTEK
    emhlbndlaS5saTokYXByMSRQWDhjcUVDaiQ1enZDM2VCMXZoTGlveWpWamRra0Uv
    CmhlbGxvOiRhcHIxJDRubFBHRXFaJG5xejJvamt1eEFZNEZVRXkwVHAzeDEK

创建ingress.yaml

定义访问路径,定义中间件

# dashboard.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: dashboard
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`traefik.test.local`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
      kind: Rule
      services:
        - name: api@internal
          kind: TraefikService
      middlewares:
        - name: test-auth

生效配置

kubectl apply -f middleware.yaml
kubectl apply -f ingress.yaml

检查打开网页时,是否弹出登录对话框

更新密码

如果我们要更新密码,可以重新使用htpasswd生成密码,然后放在policy文件中,使用命令行更新

kubectl create secret generic authsecret --from-file=users=./policy --dry-run=client -o yaml | kubectl apply -f -

 


苏ICP备18047533号-1