Category Archive : K8S相关

在Kubernetes里使用Traefik插件实现IP黑名单功能

背景

网上关于traefik的ip黑名单功能的文章几乎没有,ChatGPT讲解的也不太对,于是我根据自己的使用经验记录下在kubernetes里,使用traefik的denyip插件,配置IP黑名单功能,可以对单独的ingress(域名)生效,也可以对整个entrypoint(端口)生效


traefik的denyip安装

1.去traefik插件中间,找到denyip插件的信息

https://plugins.traefik.io/plugins/62947363ffc0cd18356a97d1/deny-ip-plugin

2.参考traefik官方helmchart编写values-traefik.yaml文件(https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml)

# 这里只贴下载插件模块的代码
experimental:
  plugins:
    denyip:
      moduleName: github.com/kevtainer/denyip
      version: v1.0.0

3.安装traefik

helm upgrade --install traefik -n traefik -f values-traefik.yaml traefik/traefik --version 26.0.0
#安装完之后,可以检查pod,会发现在pod配置里添加上了插件信息
kubectl describe pod -n traefik traefik-txxqm

对整个entrypoint限制IP访问(例如限制IP访问80端口下的所有域名)

1.添加middleware.yaml文件,例如禁用192.168.1.1的IP访问

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
    name: denyip
    namespace: traefik
spec:
    plugin:
        denyip:
            ipDenyList:
                - 192.168.1.1

添加完后,使用kubectl apply -f middleware.yaml命令使其生效

2.编辑values-traefik.yaml文件,修改ports部分,找到80端口部分,修改代码如下,只生效于80端口,443端口不生效。

ports:
  web:
    port: 80
    expose: true
    exposedPort: 80 # 对外的 HTTP 端口号,使用标准端口号在国内需备案
    middlewares:
     - traefik-denyip@kubernetescrd
  websecure:
    port: 443
    expose: true
    exposedPort: 443 # 对外的 HTTPS 端口号,使用标准端口号在国内需备案

3.再次安装traeifk

helm upgrade –install traefik -n traefik -f values-traefik.yaml traefik/traefik –version 26.0.0

4,去dashboard页面检查,会发现在这个entrypoint的所有ingress的配置里,都会加上该middleware


对单一IngressRoute生效(例如对指定域名或路径)

1.添加middleware.yaml文件,例如禁用192.168.1.1的IP访问

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
    name: denyip
    namespace: traefik
spec:
    plugin:
        denyip:
            ipDenyList:
                - 192.168.1.1

添加完后,使用kubectl apply -f middleware.yaml命令使其生效

2.这里有一个注意点,往往我们的ingress和middleware不一定在同一个namespace,这个时候需要在安装traefik的时候启用【允许使用跨命名空间】功能,修改values-traefik.yaml,添加如下配置:

providers:
  kubernetesCRD:
    allowCrossNamespace: true

然后再次安装

helm upgrade --install traefik -n traefik -f values-traefik.yaml traefik/traefik --version 26.0.0

3.修改需要使用middleware的ingressroute文件,和service同级增加middlewares信息

4.修改完成后,再去dashboard页面检查,会发现只有该http route有middleware信息,不会影响其他域名的正常访问

强制删除rancher里托管的K8S集群

背景

有时候,我们会出现,虚拟机先删除了,然后才想起来rancher里还有个集群没删掉,这个时候,再通过rancher的界面去删除托管集群,往往会一直卡在“当前集群Removing中” 那么这种情况下,该如何处理呢?

我翻到了这么一个答案:https://forums.rancher.com/t/unable-to-remove-cluster/13032/9

让我们一起来实际操作一下吧

1.找到卡住集群的名字

1.点击待删除集群的名字,进入集群的详情页,复制URL中C/后面的字符,例如我的集群是c-9rhjh

2.登录部署rancher的服务器,找到卡住集群

输入命令kubectl get clusters.management.cattle.io 就可以看到我们卡住的集群

3,通过kubectl edit 功能,设置finalizers字段为[]

输入 kubectl edit clusters.management.cattle.io c-9rhjh

保存之后,回到rancher的GUI界面,你会发现此集群已经消失

trivy与droneCI结合,扫描容器安全

trivy是什么?

一款简单的安全扫描工具,扫描范围如下:

  • OS packages and software dependencies in use (SBOM)
  • Known vulnerabilities (CVEs)
  • IaC misconfigurations
  • Sensitive information and secrets

我们选择它的主要原因是,1.它能以docker的方式运行,通过把病毒库缓存挂载在NFS中,避免每次CI都去拉取病毒库。2.它的扫描速度快,10秒钟之内能结束战斗。3.它的扫描对象可以是容器镜像,直接扫描我们业务代码生成的镜像。

4.当扫描出安全漏洞时,我们可以更新基础镜像,一次性解决安全问题,并且再次运行trivy,快速检查。


droneCI里怎么使用trivy呢?

1.每晚定时更新trivy数据库,避开trivy每次自动更新,因为默认trivy上游仓库是6小时更新一次。如果没有自动更新,很有可能巧了,就刚好白天有次drone流水线业务代码提交遇上了trivy,那就花时间久了

附上drone流水线-trivy仓库的参考代码。把更新好的数据库,挂载到一个nfs缓存里

---
kind: pipeline
type: kubernetes
name: download trivy db
steps:
  - name: trivy download db
    image: aquasec/trivy:0.29.2
    commands:
      - "trivy image --download-db-only"
    volumes:
      - name: trivy-cache
        path: /root/.cache/
trigger:
  event:
    - cron
volumes:
  - name: trivy-cache
    host:
      path: /home/nfs/cache/trivy

2.以python仓库为例子,我们在生成镜像后,通过trivy images –exit-code 1 {镜像名称} 功能检查,如果有安全漏洞,会返回exit-code 1流水线结束

---
kind: pipeline
type: kubernetes
name: push
steps:
  - name: buildimages
    image: gcr.io/kaniko-project/executor:v1.8.1
    command: [此处省去build 镜像命令]
  - name: trivy
    image: aquasec/trivy:0.29.2
    commands:
      - "trivy image --skip-db-update --security-checks vuln --exit-code 1 python:latest"
    volumes:
      - name: trivy-cache
        path: /root/.cache/
trigger:
  event:
    - push
volumes:
  - name: trivy-cache
    host:
      path: /home/nfs/cache/trivy

流水线效果

通过工具来规范代码提交与发布日志

背景

1.因为我们的现场版本发布是比较频繁的,关于本次发布为了解决什么问题,修复了什么BUG。这个全靠项目经理自觉,并不是每次发布都有写,也不一定每次都能写的全,我想有一个能兜底的机制。

2.这个时候我想起了github发布版本时有一个“auto-generate release notes”功能,它通过 git logs 命令,将两次版本变化之间的git message信息导出成markdown格式的文档,如果我们的gitea有这样一个工具,那就太棒了.

3.在gitea官网和github上进行了查阅,发现gitea目前不打算抄这个功能,未来也不会做,而我们的git服务端用的是gitea,于是我想通过drone CI流水线来实现这个功能

4.通过查阅,我发现nodejs有一个很火的工具https://github.com/conventional-changelog/standard-version 能够完成这件事,只要我们开发人员提交的 git message是符合https://www.conventionalcommits.org/https://semver.org/规范的即可

5.根据这个规范,找到vscode里有一个插件”Conventional Commits”工具是非常契合的

6.结局是我们的开发人员,只需要在vscode上安装好这个”Conventional Commits”工具,并且根据工具提示来提交代码,后续就可以自动生成changelog了,从而达到标准化“代码提交”和“发布日志”的目的

效果图

下面这个Changelog由发布之后的流水线自动生成

开发人员做什么

在vscode里下载该插件,插件扩展ID vivaxy.vscode-conventional-commits

提交代码的时候,按照工具提示一路选择即可

运维人员做什么

根据https://github.com/conventional-changelog/standard-version库来编写一个简单的流水线工具

这里我用的代码是python,流水线运行环境是DroneCI,git服务端是gitea

废话不多说,下面直接上代码,可以通过 docker build命令

docker build -t drone-changelog-plugin:1.0.1 .

去制作drone的流水线镜像

main.py

import os
import shutil

import requests


def search_tag_info(git_url,token, repo):
    '''
    搜索git上的tag信息
    :param git_url: git 的地址
    :param token: git 的access token
    :param repo: git 待查询的git仓库信息
    :return: tag信息集合
    '''
    url = "https://"+git_url+"/api/v1/repos/" + repo + "/releases"
    headers = {'accept': 'application/json',
               'Authorization': 'token ' + token,
               'Content-Type': 'application/json'
               }
    response = requests.get(url, headers=headers)
    tag_list = response.json()
    return tag_list


def modify_tag_body(git_url,token, repo, tag_id, body):
    '''
    修改git仓库的tag里的内容
    :param git_url: git 的地址
    :param token: git 的access token
    :param repo: git 待查询的git仓库信息
    :param tag_id: git tag的id信息
    :param body: 需要修改的内容,一般是changelog.md文件
    :return:
    '''
    url = "https://"+git_url+"/api/v1/repos/" + repo + "/releases/" + str(tag_id)
    headers = {'accept': 'application/json',
               'Authorization': 'token ' + token,
               'Content-Type': 'application/json'
               }
    body = {
        "body": body
    }
    response = requests.patch(url, json=body, headers=headers)


if __name__ == '__main__':
    print(os.environ)
    tag = os.environ["DRONE_TAG"]
    branch = os.environ["DRONE_REPO_BRANCH"]
    username = os.environ["DRONE_NETRC_USERNAME"]
    password = os.environ["DRONE_NETRC_PASSWORD"]
    git_repo = os.environ["DRONE_REPO"]
    git_token = os.environ["PLUGIN_TOKEN"]
    src_file = "/lzw/.versionrc"
    dst_foleder = os.environ["DRONE_WORKSPACE"]
    git_url= os.environ["PLUGIN_GIT_SERVER"]
    shutil.copy(src_file, dst_foleder)

    # 获取当前仓库里所有的tag信息
    tag_info_list = search_tag_info(git_url,git_token, git_repo)
    # 获取最新的一个tag
    now_tag = tag_info_list[0]
    # 获取所有的tag信息,这个地方是因为standard-version的特性,
    # 要获取所有的tag信息,再删掉当前的tag信息,
    # 再使用standard-version 重新生成一个tag信息
    os.system("git remote set-url origin https://'" + username + "':'" + password + "'@"+git_url+"/" + git_repo)
    os.system("git fetch --all")
    os.system("git tag -d " + tag)
    cmd = 'standard-version --tag-prefix "" --release-as ' + tag
    print(cmd)
    os.system(cmd)
    # standard-version命令会产生一个CHANGELOG.md文件
    with open('CHANGELOG.md', encoding='utf-8') as f:
        line = f.read()
    print("tag len is:" + str(len(tag_info_list)))
    # 判断一下目前有几个tag,分割保留最新的git tag 变更信息
    if len(tag_info_list) > 1:
        pre = tag_info_list[1]
        list = line.split(" " + pre['tag_name'] + " ")
        # print(list[0])
        now_content = list[0]
    else:
        now_content = line
    # 把CHANGELOG.md中本次变更的传到git的tag信息里
    modify_tag_body(git_url,git_token, git_repo, now_tag['id'], now_content)

Dockerfile

FROM python:3.9-buster
LABEL maintainer="357244849@qq.com"

RUN set -eux \
    && sed -i "s@http://ftp.debian.org@https://repo.huaweicloud.com@g" /etc/apt/sources.list \
    && sed -i "s@http://security.debian.org@https://repo.huaweicloud.com@g" /etc/apt/sources.list \
    && apt-get update \
    && apt-get install -y nodejs npm
ENV NODE_PATH="/usr/lib/node_modules"
ENV LANG=C.UTF-8 PYTHONUNBUFFERED=1
RUN npm i -g standard-version
WORKDIR /lzw
ADD . /lzw
RUN pip install -r /lzw/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
CMD ["python3","/lzw/main.py"]

requirements.txt

requests

.versionrc

{
  "types": [
    {"type": "feat","section": "Features"},
    {"type": "fix","section": "Bug Fixes"},
    {"type": "docs","section": "Documentation" },
    {"type": "style","section": "Styling" },
    {"type": "refactor","section": "Refactors" },
    {"type": "perf","section": "Performance" },
    {"type": "test","section": "Tests" },
    {"type": "build","section": "Build System" },
    {"type": "ci","section": "CI" },
    {"type": "chore","section": "Chore","hidden":true },
    {"type": "revert","section": "Reverts" }
  ]
}

如何嵌入到DroneCI流水线呢

在drone.yml文件里嵌入如下yml即可

---
kind: pipeline
type: kubernetes
name: tag
steps:
  - name: generator change log
    image: drone-changelog-plugin:1.0.1
    settings:
      token: XXXXXX
      git_url: XXXXXX
trigger:
  event:
    - tag

一篇文章带你入门K8S二次开发

背景

我们经常会在网上看到K8S和周边工具的教程,例如HELM的使用,droneCI的使用,但是很少有文章写,如何基于K8S进行二次开发,本篇文章将使用python和vue进行K8S的二次开发,实现一个简单的查询k8s的pod和node信息的页面


效果图

通过前端页面,调用后端python接口,查询k8s当前的节点状态和应用状态


涉及到的知识点

知识点说明
python-sanic库为前台提供API接口
python-kubernetes库访问k8s,获取pod和node资源信息
nodejs-vue前端框架
nodejs-element-UI提供UI组件,用了图标和表格组件
k8s-helm程序最后是要运行在K8S里,所以要编写helm包,包括rbac,svc,deployment文件
docker前后端的docker镜像制作

用户故事


后端python代码解说

main.py 主函数入口

#main.py
from kubernetes import client, config
from sanic import Sanic
from sanic.response import json

from cors import add_cors_headers
from options import setup_options

# sanic程序必须有个名字
app = Sanic("backend")
# 在本地调试,把config文件拷贝到本机的~/.kube/config然后使用load_kube_config,在K8S集群里使用load_incluster_config
# config.load_kube_config()
config.load_incluster_config()


def check_node_status(receiver):
    '''
    检查节点的状态是否正确,正确的设为1,不正确的设为0
    '''
    # 期望结果
    expect = {"NetworkUnavailable": "False",
              "MemoryPressure": "False",
              "DiskPressure": "False",
              "PIDPressure": "False",
              "Ready": "True"
              }
    result_dict = {}
    for (key, value) in receiver.items():
        # 这个逻辑是判断k8s传过来的值与expect的值是否相同
        if expect[key] == value:
            result_dict[key] = 1
        else:
            result_dict[key] = 0
    return result_dict


@app.route("/api/node")
async def node(request):
    result = []
    v1 = client.CoreV1Api()
    node_rest = v1.list_node_with_http_info()
    for i in node_rest[0].items:
        computer_ip = i.status.addresses[0].address
        computer_name = i.status.addresses[1].address
        # 先获得节点的IP和名字
        info = {"computer_ip": computer_ip, "computer_name": computer_name}
        status_json = {}
        # 节点有多个状态,把所有状态查出来,存入json里
        # 这里有一个flannel插件的坑,及时节点关机了,NetworkUnavailable查出来还是False
        for node_condition in i.status.conditions:
            status_json[node_condition.type] = node_condition.status
        check_dict = check_node_status(status_json)
        # 把节点的状态加入节点信息json里
        info.update(check_dict)
        # 把每一个节点的查询结果加入list里,返回给前端
        result.append(info)
    return json(result)


@app.route("/api/pod")
async def pod(request):
    '''
    接口名是pod,其实是检查所有的deployment,statefulset,daemonset的副本状态
    通过这些状态判断当前的程序是否正常工作
    '''
    pod_list = []
    apis_api = client.AppsV1Api()
    # 检查deployment信息
    resp = apis_api.list_deployment_for_all_namespaces()
    for i in resp.items:
        pod_name = i.metadata.name
        pod_namespace = i.metadata.namespace
        pod_unavailable_replicas = i.status.unavailable_replicas
        # 不可用副本状态为None表示没有不可用的副本,程序正常
        if pod_unavailable_replicas == None:
            pod_status = 1
        else:
            pod_status = 0
        pod_json = {"pod_namespace": pod_namespace, "pod_name": pod_name, "pod_status": pod_status}
        pod_list.append(pod_json)
    # 检查stateful_set信息
    resp_stateful = apis_api.list_stateful_set_for_all_namespaces()
    for i in resp_stateful.items:
        pod_name = i.metadata.name
        pod_namespace = i.metadata.namespace
        # 正常工作的副本数量,等于期望的副本数量时,表明程序是可用的
        if i.status.ready_replicas == i.status.replicas:
            pod_status = 1
        else:
            pod_status = 0
        pod_json = {"pod_namespace": pod_namespace, "pod_name": pod_name, "pod_status": pod_status}
        pod_list.append(pod_json)
    # 检查daemonset信息
    resp_daemonset = apis_api.list_daemon_set_for_all_namespaces()
    for i in resp_daemonset.items:
        pod_name = i.metadata.name
        pod_namespace = i.metadata.namespace
        # 不可用副本状态为None表示没有不可用的副本,程序正常
        if i.status.number_unavailable == None:
            pod_status = 1
        else:
            pod_status = 0
        pod_json = {"pod_namespace": pod_namespace, "pod_name": pod_name, "pod_status": pod_status}
        pod_list.append(pod_json)
    return json(pod_list)


# Add OPTIONS handlers to any route that is missing it
app.register_listener(setup_options, "before_server_start")

# Fill in CORS headers
app.register_middleware(add_cors_headers, "response")
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

cors.py 解决跨域问题,主要是本地调试方便,放到我的helm包里部署到K8S上时,是不需要的,因为我会用nginx把他反向代理过去

#cors.py
from typing import Iterable


def _add_cors_headers(response, methods: Iterable[str]) -> None:
    '''
    为了在测试的时候偷懒,我把Access-Control-Allow-Origin设置成了*
    如果是做成镜像和我的helm包一起用,是不需要这样的,因为我会用nginx把后端和前端设置成同源
    '''
    allow_methods = list(set(methods))
    if "OPTIONS" not in allow_methods:
        allow_methods.append("OPTIONS")
    headers = {
        "Access-Control-Allow-Methods": ",".join(allow_methods),
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Credentials": "true",
        "Access-Control-Allow-Headers": (
            "origin, content-type, accept, "
            "authorization, x-xsrf-token, x-request-id"
        ),
    }
    response.headers.extend(headers)


def add_cors_headers(request, response):
    if request.method != "OPTIONS":
        methods = [method for method in request.route.methods]
        _add_cors_headers(response, methods)

options.py 搭配上面的cors.py使用

# options.py
from collections import defaultdict
from typing import Dict, FrozenSet

from sanic import Sanic, response
from sanic.router import Route

from cors import _add_cors_headers


def _compile_routes_needing_options(
        routes: Dict[str, Route]
) -> Dict[str, FrozenSet]:
    needs_options = defaultdict(list)
    # This is 21.12 and later. You will need to change this for older versions.
    for route in routes.values():
        if "OPTIONS" not in route.methods:
            needs_options[route.uri].extend(route.methods)

    return {
        uri: frozenset(methods) for uri, methods in dict(needs_options).items()
    }


def _options_wrapper(handler, methods):
    def wrapped_handler(request, *args, **kwargs):
        nonlocal methods
        return handler(request, methods)

    return wrapped_handler


async def options_handler(request, methods) -> response.HTTPResponse:
    resp = response.empty()
    _add_cors_headers(resp, methods)
    return resp


def setup_options(app: Sanic, _):
    app.router.reset()
    needs_options = _compile_routes_needing_options(app.router.routes_all)
    for uri, methods in needs_options.items():
        app.add_route(
            _options_wrapper(options_handler, methods),
            uri,
            methods=["OPTIONS"],
        )
    app.router.finalize()

requirements.txt 放置python需要用到的sdk

aiofiles==0.8.0
cachetools==4.2.4
certifi==2021.10.8
charset-normalizer==2.0.10
google-auth==2.3.3
httptools==0.3.0
idna==3.3
Jinja2==3.0.3
kubernetes==21.7.0
MarkupSafe==2.0.1
multidict==5.2.0
oauthlib==3.1.1
pyasn1==0.4.8
pyasn1-modules==0.2.8
python-dateutil==2.8.2
PyYAML==6.0
requests==2.27.1
requests-oauthlib==1.3.0
rsa==4.8
sanic==21.12.1
sanic-ext==21.12.3
sanic-routing==0.7.2
six==1.16.0
urllib3==1.26.8
websocket-client==1.2.3
websockets==10.1

Dockerfile 打包后端代码成镜像使用 docker build -t k8s-backend .

FROM python:3.9
ADD . .
RUN pip install -r /requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
WORKDIR .
CMD ["python3","main.py"]

前端代码解说

主要就是App.vue和main.ts两个文件

这里省略nodejs和vue的安装过程,使用下面的命令创建一个vue3的项目

# 下载vue的过程省略,创建一个vue项目,创建的时候,选择typescript版本
vue create k8s-frontend
# 安装element-ui的vue3版本
npm install element-plus --save
# npm安装axios,用于向后台发起请求
npm i axios -S

main.ts 主入口

import { Component, createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)

app.use(ElementPlus)

app.mount('#app')

App.vue 做demo图省事,我就用了这一个vue文件放了所有功能

<template>

  <h2>服务器信息</h2>
  <el-table 
    :data="tableData" style="width: 100%">
    <el-table-column prop="computer_ip" label="IP地址" width="180" />
    <el-table-column prop="computer_name" label="服务器名字" width="180" />
    <el-icon><check /></el-icon>
    <el-table-column label="网络插件" width="100">
      <template #default="scope">
        <el-icon :size="20">
          <check class="check" v-if="scope.row.NetworkUnavailable ==1" />
          <close class="close" v-else />
        </el-icon>
      </template>
    </el-table-column>
    <el-table-column label="内存压力" width="100">
      <template #default="scope">
        <el-icon :size="20">
          <check class="check" v-if="scope.row.MemoryPressure ==1" />
          <close class="close" v-else />
        </el-icon>
      </template>
    </el-table-column>
    <el-table-column label="硬盘压力" width="100">
      <template #default="scope">
        <el-icon :size="20">
          <check class="check" v-if="scope.row.DiskPressure ==1" />
          <close class="close" v-else />
        </el-icon>
      </template>
    </el-table-column>
    <el-table-column label="进程压力" width="100">
      <template #default="scope">
        <el-icon :size="20">
          <check class="check" v-if="scope.row.PIDPressure ==1" />
          <close class="close" v-else />
        </el-icon>
      </template>
    </el-table-column>
    <el-table-column label="K3S状态" width="100">
      <template #default="scope">
        <el-icon :size="20">
          <check class="check" v-if="scope.row.Ready ==1" />
          <close class="close" v-else />
        </el-icon>
      </template>
    </el-table-column>
  </el-table>
  <el-divider></el-divider>
  <h2>应用程序信息</h2>
  <el-table
    :data="podData"
    style="width: 100%"
    :default-sort="{ prop: 'pod_status', order: 'ascending' }"
  >
    <el-table-column prop="pod_namespace" sortable  label="命名空间" width="180" />
    <el-table-column prop="pod_name" sortable label="应用名字" width="180" />
    <el-icon><check /></el-icon>
    <el-table-column prop="pod_status" label="是否正常" sortable width="100">
      <template #default="scope">
        <el-icon :size="20">
          <check class="check" v-if="scope.row.pod_status ==1" />
          <close class="close" v-else />
        </el-icon>
      </template>
    </el-table-column>
  </el-table>
  <el-divider></el-divider>
</template>

<script lang="ts" >
import { Options, Vue } from 'vue-class-component';
import { Check, Close } from '@element-plus/icons-vue';
import axios from 'axios'

@Options({
    // 这里可以配置Vue组件支持的各种选项
    components: {
        Check,
        Close
    },
    data() {
        return {
          podData: [],
          tableData: [],
        }
    },
    mounted() {
      this.pod();
      this.show();
    },
    methods: {
        say(){
          console.log("say");
        },
        pod(){
          const path = "http://127.0.0.1:8000/api/pod";
          //本地调试使用,在服务器上还是用相对路径
          // const path = "http://127.0.0.1:8000/node";
          // 务必使用箭头函数的方法,这样this.id能直接对上,不然会报错提示id没找到
          axios.get(path).then((response) => {
            this.podData = response.data;
          });
        },
        show() {
        const path = "http://127.0.0.1:8000/api/node";
        //本地调试使用,在服务器上还是用相对路径
        // const path = "http://127.0.0.1:8000/node";
        // 务必使用箭头函数的方法,这样this.id能直接对上,不然会报错提示id没找到
        axios.get(path).then((response) => {
          this.tableData = response.data;
        });
      },
    }
})
export default class App extends Vue {
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: left;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Dockerfile 用于制作前端镜像 docker build -t k8s-frontend .

FROM  node:14-alpine3.12 AS build

LABEL maintainer="sunj@sfere-elec.com"

ADD . /build/

RUN set -eux \
    && yarn config set registry https://mirrors.huaweicloud.com/repository/npm/ \
    && yarn config set sass_binary_site https://mirrors.huaweicloud.com/node-sass \
    && yarn config set python_mirror https://mirrors.huaweicloud.com/python \
    && yarn global add yrm \
    && yrm add sfere http://repo.sfere.local:8081/repository/npm-group/ \
    && yrm use sfere \
    && cd /build \
    && yarn install \
    && yarn build

FROM nginx:1.21.5-alpine
LABEL zhenwei.li "zhenwei.li@sfere-elec.com"
COPY --from=build /build/dist/ /usr/share/nginx/html
# 暴露端口映射
EXPOSE 80

HELM包解说

deployment.yaml 把两个docker镜像放在同一个deployment里

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: {{ .Release.Name }}
  name: {{ .Release.Name }}
spec:
  replicas: 1
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      containers:
        - image: k8s-check-backend
          imagePullPolicy: Always
          name: server-check-backend
          resources: {}
        - image: k8s-check-frontend
          imagePullPolicy: Always
          name: server-check-frontend
          resources: {}
          volumeMounts:
          - name: nginx-conf
            mountPath: /etc/nginx/conf.d/default.conf
            subPath: default.conf
      restartPolicy: Always
      volumes:
        - name: nginx-conf
          configMap:
            name: {{ .Release.Name }}
            items:
            - key: default.conf
              path: default.conf
      serviceAccountName: {{ .Release.Name }}

service.yaml 把前端通过nodeport方式暴露出去,方便测试

apiVersion: v1
kind: Service
metadata:
  labels:
    app: {{ .Release.Name }}
  name: {{ .Release.Name }}
spec:
  type: NodePort
  ports:
    - name: web
      port: 80
      targetPort: 80
      nodePort: 32666
  selector:
    app: {{ .Release.Name }}

configmap.yaml nginx的配置文件,反向代理后端

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}
data:
  default.conf: |
    # 当前项目nginx配置文件,lzw
    server {
        listen       80;
        server_name  _A;
        gzip on;
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

        location / {
            root /usr/share/nginx/html;
            index index.html index.htm;

            if (!-e $request_filename){
                    rewrite ^/.* /index.html last;
            }
        }
        location /api {
            proxy_pass          http://localhost:8000;
            proxy_http_version 1.1;
            proxy_set_header    X-Real-IP           $remote_addr;
            proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        }

        error_page   500 502 503 504  /50x.html;
    }

rbac.yaml 我们的程序是需要访问k8s资源的,如果没有配置rbac,调用K8S的API会报403错误

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/name: {{ .Release.Name }}
  name: {{ .Release.Name }}
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: {{ .Release.Name }}
subjects:
- kind: ServiceAccount
  name: {{ .Release.Name }}
  namespace: {{ .Release.Namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/name: {{ .Release.Name }}
  name: {{ .Release.Name }}
rules:
- apiGroups:
  - ""
  resources:
  - configmaps
  - secrets
  - nodes
  - pods
  - services
  - resourcequotas
  - replicationcontrollers
  - limitranges
  - persistentvolumeclaims
  - persistentvolumes
  - namespaces
  - endpoints
  verbs:
  - list
  - watch
- apiGroups:
  - extensions
  resources:
  - daemonsets
  - deployments
  - replicasets
  - ingresses
  verbs:
  - list
  - watch
- apiGroups:
  - apps
  resources:
  - statefulsets
  - daemonsets
  - deployments
  - replicasets
  verbs:
  - list
  - watch
---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/name: {{ .Release.Name }}
  name: {{ .Release.Name }}
  namespace: {{ .Release.Namespace }}

程序安装

helm install k8s-check helm/k8s-server-check

安装完成后,通过http://masterip:32666访问即可

Argo CD接入LDAP认证或者gitea认证的方法

背景

argocd默认是通过修改argocd-cm来添加账户的,添加完账户后,还需要使用argocd客户端命令去给账户设置密码,这肯定是比较麻烦的,为了方便使用,我们可以接入ldap认证或者gitea的oauth2认证。

这里我们主要写ldap认证,因为gitea没有提供“组信息”给dex,而ldap能返回”组信息”

关键词:argocd ldap dex

看图讲故事

根据上面的图,我们可以看到,主要是通过配置argocd-cm和argocd-rbac-cm两个配置文件来生效的

下面我们来详细讲讲配置文件如何编写,关于gitea,ldap的安装这里就不再描述了,简单提一句argocd的安装

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

接入LDAP的配置

编写一个ldap-patch-dex.yaml

注意:这里有一个坑爹的地方,DN居然要大写才能使用,官网文档没有说要大写

apiVersion: v1
data:
  dex.config: |
    connectors:
    - type: ldap
      name: 统一账户中心
      id: ldap
      config:
        # Ldap server address
        host: ${LDAP地址}:${LDAP端口}
        insecureNoSSL: true
        insecureSkipVerify: true
        # Variable name stores ldap bindDN in argocd-secret
        bindDN: "$dex.ldap.bindDN"
        # Variable name stores ldap bind password in argocd-secret
        bindPW: "$dex.ldap.bindPW"
        usernamePrompt: 用户名
        # Ldap user serch attributes
        userSearch:
          baseDN: "ou=XXXX,dc=XXX,dc=com"
          filter: "(objectClass=person)"
          username: uid
          idAttr: uid
          emailAttr: mail
          nameAttr: cn
        # Ldap group serch attributes
        groupSearch:
          baseDN: "dc=XXX,dc=com"
          filter: "(objectClass=groupOfUniqueNames)"
          userAttr: DN
          groupAttr: uniqueMember
          nameAttr: cn
kubectl -n argocd patch configmaps argocd-cm --patch "$(cat ldap-patch-dex.yaml)"

上面的 bindPW 和 bindDN 我们放一个只读权限的账户到secret里,设置方法如下

kubectl -n argocd patch secrets argocd-secret --patch "{\"data\":{\"dex.ldap.bindPW\":\"$(echo my-password | base64 -w 0)\"}}"

kubectl -n argocd patch secrets argocd-secret --patch "{\"data\":{\"dex.ldap.bindDN\":\"$(echo CN=ldapuser,OU=Service Accounts,OU=Resource,DC=mydomain,DC=local | base64 -w 0)\"}}"

设置grooup权限(只有ldap能分组,gitea接入不能获取分组)

编辑argocd-rbac-cm 文件,这里举例设置 “administrators “组为管理员

kubectl edit configmaps -n argocd argocd-rbac-cm

apiVersion: v1
data:
  policy.csv: |
    g, administrators, role:admin
  policy.default: role:readonly

编辑完成之后,需要重启argocd和dex

kubectl delete pod -n argocd argocd-dex-server-7857b96dbb-s596m
kubectl delete pod -n argocd argocd-server-559f498454-fl5d2

效果演示



不推荐使用(接入gitea oauth2认证)

这个 接入gitea oauth2 认证我不推荐,因为没有办法设置“组”,所有用户通过这种方式登录进来的都是 policy.default 对应的权限,也许以后会有,但是笔者写这篇文章的时候是没有办法获取“组”的。

1,在gitea里输入重定向URI创建oauth2认证,获得clientID和clientSecret。

注意:argocd的重定向地址是固定后缀/api/dex/callback

2.创建一个gitea-patch-dex.yaml 内容如下

apiVersion: v1
data:
  accounts.drone: apiKey,login
  dex.config: |-
    connectors:
    - type: gitea
      name: Gitea
      id: gitea
      config:
        baseURL: https://gitea域名
        redirectURI: https://argocd域名/api/dex/callback
        clientID: 上一步获取的clientID
        clientSecret: 上一步获取的clientSecret

3.生效配置文件,重启dex

kubectl -n argocd patch configmaps argocd-cm --patch "$(cat ldap-patch-dex.yaml)"

kubectl delete pod -n argocd argocd-dex-server-7857b96dbb-s596m


electron+droneCI+minio流水线

背景

因为我们的electron程序已经开发完成,期望要能开发人员每次上传代码,打了tag就自动build一份deb文件,自动上传到minio,方便运维人员去拿deb文件部署到ubuntu环境上。我们已有的技术栈包含droneCI,minio,python,于是边有了该方案。本文省略了vault,ldap,minio,harbor的安装与配置,这些程序的安装配置在本网站的其他文章里,就不一一贴出来了


架构图

解释:

1.前端开发上传electron代码到git服务端

2.git服务端通过webhook方式通知drone-server产生了。例如本文只测试的是发布tag触发webhook,还有很多种触发方式都可以设置

3.drone-server收到通知后,再在drone-runner所在的k8s集群里启动一个包含nodejs和python的任务容器

4.任务容器通过electron-forge make 命令打包一个deb文件

5.任务容器通过minio提供的python sdk上传deb文件到minio


drone插件编写

要完成上述目标,第一步就是得编写一个drone的插件

我编写该插件使用的是nodejs16版本的debian系统,然后通过提前安装好需要的如下表格里的工具。注意,因为我用的是华为源,2021年12月9日的时候,华为镜像上最新的electron只到16.0.2版本,所以注意指定版本号

介绍:该插件使用nodejs16版本的debian系统,然后通过提前安装好需要的如下工具。注意,因为我用的是华为源,2021年12月9日的时候,华为镜像上最新的electron只到16.0.2版本,所以注意指定版本号

工具名
rpm
python3-pip
python3
fakeroot
electron@v16.0.2 
electron-prebuilt-compile
electron-forge 
dpkg
minio的python sdk

代码有3个文件main.py Dockerfile ,requirements.txt,下面是详细介绍

main.py

代码功能是先获取环境变量,然后使用git的tag号替换掉package.json里的version字段。执行yarn install,yanr make,通过环境变量找到需要上传的文件,通过pythonde的sdk上传到minio里。详细代码如下

#main.py
import json
import os
import subprocess

from minio import Minio
from minio.error import S3Error

endpoint = "minio.sfere.local"
access_key = "bababa"
secret_key = "bababa"
bucket = "electronjs"
folder_path = "/drone/src/out/make/deb/x64"
suffix = "deb"
tag = "0.0.0"


def find_file_by_suffix(target_dir, target_suffix="deb"):
    find_res = []
    target_suffix_dot = "." + target_suffix
    walk_generator = os.walk(target_dir)
    for root_path, dirs, files in walk_generator:
        if len(files) < 1:
            continue
        for file in files:
            file_name, suffix_name = os.path.splitext(file)
            if suffix_name == target_suffix_dot:
                find_res.append(os.path.join(root_path, file))
    return find_res


def get_environment():
    global endpoint, access_key, secret_key, bucket, suffix, tag

    if "PLUGIN_ENDPOINT" in os.environ:
        endpoint = os.environ["PLUGIN_ENDPOINT"]
    if "PLUGIN_ACCESS_KEY" in os.environ:
        access_key = os.environ["PLUGIN_ACCESS_KEY"]
    if "PLUGIN_SECRET_KEY" in os.environ:
        secret_key = os.environ["PLUGIN_SECRET_KEY"]
    if "PLUGIN_BUCKET" in os.environ:
        bucket = os.environ["PLUGIN_BUCKET"]
    if "PLUGIN_SUFFIX" in os.environ:
        suffix = os.environ["PLUGIN_SUFFIX"]
    if "PLUGIN_TAG" in os.environ:
        tag = os.environ["PLUGIN_TAG"]


def yarn_make():
    with open('./package.json', 'r', encoding='utf8')as fp:
        json_data = json.load(fp)
    json_data['version'] = tag
    with open('./package.json', 'w', encoding='utf8')as fp:
        json.dump(json_data, fp, ensure_ascii=False, indent=2)
    print('package version replace to ' + tag)
    print(subprocess.run("yarn install", shell=True))
    print(subprocess.run("yarn make", shell=True))


def upload_file():
    file_list = find_file_by_suffix(folder_path, suffix)
    # 创建minio连接,这里因为我们是http的,所以secure=False
    client = Minio(
        endpoint=endpoint,
        access_key=access_key,
        secure=False,
        secret_key=secret_key,
    )

    # 检查bucket是否存在,不存在就创建bucket
    found = client.bucket_exists(bucket)
    if not found:
        client.make_bucket(bucket)
    else:
        print("Bucket 'electronjs' already exists")

    # 上传文件到bucket里
    for file in file_list:
        name = os.path.basename(file)
        client.fput_object(
            bucket, name, file,
        )
        print(
            "'" + file + "' is successfully uploaded as "
                         "object '" + name + "' to bucket '" + bucket + "'."
        )


if __name__ == "__main__":
    get_environment()
    yarn_make()
    try:
        upload_file()
    except S3Error as exc:
        print("error occurred.", exc)

Dockerfile

取一个node16版本的debian系统,使用国内源安装我们在之前列出来要用的工具,然后指定程序入口是我们的python程序。编写完后,使用docker build -t drone-electron-minio-plugin:0.1.0 . 做一个镜像上传到私仓里

FROM node:16-buster
RUN npm config set registry https://mirrors.huaweicloud.com/repository/npm/ \
    && npm config set disturl https://mirrors.huaweicloud.com/nodejs \
    && npm config set sass_binary_site https://mirrors.huaweicloud.com/node-sass \
    && npm config set phantomjs_cdnurl https://mirrors.huaweicloud.com/phantomjs \
    && npm config set chromedriver_cdnurl https://mirrors.huaweicloud.com/chromedriver \
    && npm config set operadriver_cdnurl https://mirrors.huaweicloud.com/operadriver \
    && npm config set electron_mirror https://mirrors.huaweicloud.com/electron/ \
    && npm config set python_mirror https://mirrors.huaweicloud.com/python \
    && npm config set canvas_binary_host_mirror https://npm.taobao.org/mirrors/node-canvas-prebuilt/ \
    && npm install -g npm@8.2.0 \
    && yarn config set registry https://mirrors.huaweicloud.com/repository/npm/ \
    && yarn config set disturl https://mirrors.huaweicloud.com/nodejs \
    && yarn config set sass_binary_site https://mirrors.huaweicloud.com/node-sass \
    && yarn config set phantomjs_cdnurl https://mirrors.huaweicloud.com/phantomjs \
    && yarn config set chromedriver_cdnurl https://mirrors.huaweicloud.com/chromedriver \
    && yarn config set operadriver_cdnurl https://mirrors.huaweicloud.com/operadriver \
    && yarn config set electron_mirror https://mirrors.huaweicloud.com/electron/ \
    && yarn config set python_mirror https://mirrors.huaweicloud.com/python \
    && yarn config set canvas_binary_host_mirror https://npm.taobao.org/mirrors/node-canvas-prebuilt/ \
    && yarn global add electron@v16.0.2 electron-forge electron-prebuilt-compile\
    && sed -i "s@http://ftp.debian.org@https://repo.huaweicloud.com@g" /etc/apt/sources.list \
    && sed -i "s@http://security.debian.org@https://repo.huaweicloud.com@g" /etc/apt/sources.list \
    && sed -i "s@http://deb.debian.org@https://repo.huaweicloud.com@g" /etc/apt/sources.list \
    && apt update \
    && apt install -y fakeroot dpkg rpm python3 python3-pip
ADD . .   
WORKDIR . 
RUN pip3 install -r ./requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
#CMD ["python3","/main.py"]
WORKDIR /drone/src
ENTRYPOINT ["python3", "/main.py"]

requirements.txt

minio==7.1.2

electron仓库代码

我们的electron仓库里要添加一个.drone.yml文件和对package.json稍微进行一些修改

package.json

.drone.yml

droneCI的流水线文件,使用了我们在上一节里build出来的drone插件镜像


流水线演示

需要人手动操作的

流水线自动操作的

argoCD与droneCI结合

背景

我们的CI/CD流程采用了argoCD与droneCI两款工具,droneCI的关键文件是drone.yaml文件,配置在开发代码里。 argoCD的关键是helm包和不同环境的values.yaml参数。两者需要打通起来,让开发人员专注提交业务代码,后续程序的自动更新,部署等问题不影响他们。

流程图

droneCI需要修改的部分

---
kind: pipeline
type: kubernetes
name: argocd
steps:
  - name: argocd deploy
    image: drone-argocd-plugin(自己制作的,就是一个ubuntu基础镜像,里面安装了一个argocd-cli工具)
    environment:
      ARGOCD_AUTH_TOKEN:
        from_secret: argocd_auth_token
     
      ARGOCD_SERVER:
        from_secret: argocd_server
    commands:
      - argocd --insecure app set 应用程序名 -p 应用镜像tag号=git-${DRONE_COMMIT_SHA:0:10}
      - argocd --insecure app sync 应用程序名
depends_on:
  - push
trigger:
  branch:
    - develop
  event:
    - push
---
kind: secret
name: argocd_server
get:
  path: drone/data/argocd
  name: server
---
kind: secret
name: argocd_auth_token
get:
  path: drone/data/argocd
  name: auth_token
这块代码的主要作用就是,在drone推送镜像到私有仓库之后,通过argocd的cli工具,加载argocd的登录信息,对argocd上,需要更新的应用程序设置镜像tag号,然后触发更新操作,完成自动更新

argoCD需要准备的内容

1.准备一个git仓库如下图。helm目录下放置需要部署的应用程序的所有helm文件夹,node目录放置每个节点集群需要的values.yaml文件,如开发环境是dev.yaml,测试环境是test.yaml,生产环境是prod.yaml

2.在argoCD上部署应用app,如下图所示,只要设置好对应的git仓库,helm路径,values路径即可

(此处也是笔者踏了很多坑得出的结论,最好使用git和submodule的方式来管理,不要使用harbor仓库等方式管理helm和values,因为argocd有一些设置是不符合使用习惯的,除非这个pr被合并https://github.com/argoproj/argo-cd/pull/6280)

全部打通完成之后,开发人员只要正常提交代码,通过sonarqube等扫描工具之后,代码合入develop分支之后,会触发argoCD根据git-hash自动更新,更新完成会在gitea的代码仓库上打一个小绿标,全程无人值守,666

自定义一个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


页面截图

 


苏ICP备18047533号-2