metrics Server 提供核心监控指标,比如Node节点的CPU和内存使用率,其他的监控交由 Prometheus 组件完成

简介

官网:https://prometheus.io/docs/introduction/overview/
github:https://github.com/prometheus

prometheus是基于go语言开发的一套开源的监控、报警和时间序列数据库的组合,是由SoundCloud公司开发的
开源监控系统,prometheus是CNCF(Cloud Native Computing Foundation,云原生计算基金会)继kubernetes 之后毕业的第二个项目,prometheus在容器和微服务领域中得到了广泛的应用,其特点主要如下:

- 使用 key-value 的多维度格式保存数据
- 数据不使用 MySQL 这样的传统数据库,而是使用时序数据库,目前是使用的 TSDB
- 支持第三方 dashboard 实现更高的图形界面,如 grafana(Grafana 2.5.0版本及以上)
- 功能组件化
- 不需要依赖存储,数据可以本地保存也可以远程保存
- 服务自动化发现
- 强大的数据查询语句功(PromQL:Prometheus Query Language)

TSDB

TSDB:Time Series Database,时间序列数据库,简称时序数据库

什么是时序数据库?顾名思义,就是存储与时间相关的数据,该数据是在时间上分布的一系列值

时序数据库被广泛应用于物联网和运维监控系统

这个网站可以查看各种时序数据库的排名:

小而精,性能高,数据量较小(亿级): InfluxDB
简单,数据量不大(千万级),有联合查询、关系型数据库基础:timescales
数据量较大,大数据服务基础,分布式集群需求: opentsdb、KairosDB
分布式集群需求,olap实时在线分析,资源较充足:druid
性能极致追求,数据冷热差异大:Beringei
兼顾检索加载,分布式聚合计算: elsaticsearch
如果你兼具索引和时间序列的需求。那么Druid和Elasticsearch是最好的选择。其性能都不差,同时满足检索和时间序列的特性,并且都是高可用容错架构。

阿里云版TSDB,文档写的还不错:https://help.aliyun.com/product/54825.html

Prometheus内置了TSDB,目前是V3.0版本,是一个独立维护的TSDB开源项目;在单机上,每秒可处理数百万个样本

Prometheus TSDB数据存储格式:

  • 以每2小时为一个时间窗口,并存储为一个单独的block
  • block会压缩、合并历史数据块,随着压缩合并,其block数量会减少
  • block的大小并不固定,但最小会保存两个小时的数据

P137

PromQL

https://songjiayang.gitbooks.io/prometheus/content/promql/summary.html

PromQL:Prometheus Query Language,是 Prometheus 自己开发的数据查询 DSL 语言,语言表现力非常丰富,内置函数很多,在日常数据可视化以及rule 告警中都会使用到它

系统架构

prometheus server:主服务,接受外部http请求,收集、存储与查询数据等
prometheus targets: 静态收集的目标服务数据
service discovery:动态发现服务
prometheus alerting:报警通知
push gateway:数据收集代理服务器(类似于zabbix proxy)
data visualization and export: 数据可视化与数据导出(访问客户端)

安装

三种安装方式:docker、二进制、operator

https://prometheus.io/download/ #官方二进制下载及安装,prometheus server的监听端口为9090
https://prometheus.io/docs/prometheus/latest/installation/ #docker镜像直接启动
https://github.com/coreos/kube-prometheus #operator部署

一般不使用docker安装,推荐operator,但是operator安装会隐藏很多细节,所以要先学习二进制安装

https://github.com/prometheus/prometheus/releases/download/v2.25.2/prometheus-2.25.2.linux-amd64.tar.gz
https://github.com/prometheus/alertmanager/releases/download/v0.21.0/alertmanager-0.21.0.linux-amd64.tar.gz
https://github.com/prometheus/blackbox_exporter/releases/download/v0.18.0/blackbox_exporter-0.18.0.linux-amd64.tar.gz
https://github.com/prometheus/consul_exporter/releases/download/v0.7.1/consul_exporter-0.7.1.linux-amd64.tar.gz
https://github.com/prometheus/graphite_exporter/releases/download/v0.9.0/graphite_exporter-0.9.0.linux-amd64.tar.gz
https://github.com/prometheus/haproxy_exporter/releases/download/v0.12.0/haproxy_exporter-0.12.0.linux-amd64.tar.gz
https://github.com/prometheus/memcached_exporter/releases/download/v0.8.0/memcached_exporter-0.8.0.linux-amd64.tar.gz
https://github.com/prometheus/mysqld_exporter/releases/download/v0.12.1/mysqld_exporter-0.12.1.linux-amd64.tar.gz
https://github.com/prometheus/node_exporter/releases/download/v1.1.2/node_exporter-1.1.2.linux-amd64.tar.gz
https://github.com/prometheus/pushgateway/releases/download/v1.4.0/pushgateway-1.4.0.linux-amd64.tar.gz
https://github.com/prometheus/statsd_exporter/releases/download/v0.20.0/statsd_exporter-0.20.0.linux-amd64.tar.gz

prometheus server

[root@k8s-master src]$tar zxf prometheus-2.25.2.linux-amd64.tar.gz
[root@k8s-master src]$mv prometheus-2.25.2.linux-amd64 /usr/local/prometheus
[root@k8s-master src]$cd /usr/local/prometheus/

# service启动文件
[root@k8s-master prometheus]$vim /lib/systemd/system/prometheus.service
[Unit]
Description=Prometheus Server
Documentation=https://prometheus.io/docs/introduction/overview/
After=network.target
[Service]
Restart=on-failure
WorkingDirectory=/usr/local/prometheus/
ExecStart=/usr/local/prometheus/prometheus --
config.file=/usr/local/prometheus/prometheus.yml
[Install]
WantedBy=multi-user.target

[root@k8s-master prometheus]$systemctl daemon-reload
[root@k8s-master prometheus]$systemctl start prometheus.service

node exporter

收集各k8s node节点上的监控指标数据,监听端口为9100

node exporter 和 metrics server

原理都是一样的,都是从node的kublete获取数据,但是node exporter 从本机的kubelet获取数据,metrics server 从所有node的kubelet获取数据,所以 node exporter 需要在每个node都部署一份,而metrics server只部署一个就可以

安装:

在每个node都执行以下安装步骤:

$tar zxf node_exporter-1.1.2.linux-amd64.tar.gz
$mv node_exporter-1.1.2.linux-amd64 /usr/local/node_exporter
$cd /usr/local/node_exporter/

$vim /etc/systemd/system/node-exporter.service
[Unit]
Description=Prometheus Node Exporter
After=network.target
[Service]
ExecStart=/usr/local/node_exporter/node_exporter
[Install]
WantedBy=multi-user.target

$systemctl daemon-reload 
$systemctl start node-exporter.service 

$ss -ntlp | grep 9100
LISTEN   0   32768   *:9100   *:*   users:(("node_exporter",pid=115111,fd=3)) 

与 prometheus 集成:

在prometheus server中添加一个job:

[root@k8s-master prometheus]$pwd
/usr/local/prometheus              
[root@k8s-master prometheus]$grep -v "#" prometheus.yml | grep -v "^$"
global:
alerting:
  alertmanagers:
  - static_configs:
    - targets:
rule_files:
scrape_configs:
  - job_name: 'prometheus'
    static_configs:
    - targets: ['localhost:9090']
  - job_name: 'promethues-node'		# job名称
    metrics_path: '/metrics'		# 默认就是 /metrics
    static_configs:
     - targets: ['10.0.1.31:9100', '10.0.1.32:9100', '10.0.1.33:9100']	# 被采集的node

重启 prometheus ,等一会然后观察监控情况:

haproxy exporter

除了 node_exporter,官方还提供了监控haproxy的exporter

  1. 需要将其部署在haproxy服务所在的节点

    tar xvf haproxy_exporter-0.12.0.linux-amd64.tar.gz
  2. prometheus添加haproxy数据采集

    - job_name: "prometheus-haproxy"
      static_configs:
        - targets: ["192.168.7.108:9101"]
  3. grafana添加模板

    367 2428

其他 exporter

如果官方没有提供监控相应服务的exporter,需要使用第三方或自己开发

https://github.com/zhangguanzhang/harbor_exporter
https://github.com/nginxinc/nginx-prometheus-exporter/

Grafana

调用prometheus的数据,进行更专业的可视化

cAdvisor

https://www.oschina.net/p/cadvisor

cAdvisor是由谷歌开源的 docker容器性能分析工具,cAdvisor可以对节点机器上的资源及容器进行实时监控和性能数据采集,包括CPU使用情况、内存使用情况、网络吞吐量及文件系统使用情况,cAdvisor不仅可以搜集一台机器上所有运行的容器信息,还提供基础查询界面和http接口,方便其他组件如prometheus进行数据抓取

k8s1.12之前cadvisor集成在node节点的上kubelet服务中,从1.12版本开始分离为两个组件,因此需要在node节点单独部署cadvisor

官方github给出部署方法,可是需要pull国外的镜像

# cadvisor镜像准备
$docker load -i cadvisor_v0.36.0.tar.gz
$docker tag gcr.io/google_containers/cadvisor:v0.36.0 harbor.ljk.local/k8s/cadvisor:v0.36.0
$docker push harbor.ljk.local/k8s/cadvisor:v0.36.0 

# 在每个节点 启动cadvisor容器
VERSION=v0.36.0
sudo docker run \
  --volume=/:/rootfs:ro \
  --volume=/var/run:/var/run:ro \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  --volume=/dev/disk/:/dev/disk:ro \
  --publish=8080:8080 \
  --detach=true \
  --name=cadvisor \
  --privileged \
  --device=/dev/kmsg \
harbor.ljk.local/k8s/cadvisor:${VERSION}

prometheus采集cadvisor数据

[root@k8s-master prometheus]$grep -v "#" prometheus.yml | grep -v "^$"
global:
alerting:
  alertmanagers:
  - static_configs:
    - targets:
rule_files:
scrape_configs:
  - job_name: 'prometheus'
    static_configs:
    - targets: ['localhost:9090']
  - job_name: 'promethues-node'
    metrics_path: '/metrics'
    static_configs:
     - targets: ['10.0.1.31:9100', '10.0.1.32:9100', '10.0.1.33:9100']
  - job_name: 'promethues-containers'
    metrics_path: '/metrics'
    static_configs:
     - targets: ['10.0.1.32:8080', '10.0.1.33:8080']	# cadvisor采集的node

grafana添加pod监控模板

395 893 容器模板ID

prometheus报警设置

prometheus触发一条告警的过程:

prometheus-->触发阈值-->超出持续时间-->alertmanager-->分组|抑制|静默-->媒体类型-->邮件|钉钉|微信等

分组(group): 将类似性质的警报合并为单个通知
静默(silences): 是一种简单的特定时间静音的机制,例如:服务器要升级维护可以先设置这个时间段告警静默
抑制(inhibition): 当警报发出后,停止重复发送由此警报引发的其他警报即合并一个故障引起的多个报警事件,可以消除冗余告警

安装 alertmanager

[root@k8s-master src]$tar zxf alertmanager-0.21.0.linux-amd64.tar.gz 
[root@k8s-master src]$mv alertmanager-0.21.0.linux-amd64 /usr/local/alertmanager
[root@k8s-master src]$cd /usr/local/alertmanager/

配置 alertmanager

官方配置文档:https://prometheus.io/docs/alerting/configuration/

[root@k8s-master alertmanager]$cat alertmanager.yml 
global:
  resolve_timeout: 5m
  smtp_smarthost: "smtp.qq.com:465"
  smtp_from: "441757636@qq.com"
  smtp_auth_username: "441757636@qq.com"
  smtp_auth_password: "udwthuyxjstcdhcj"	# 授权码随便写的
  smtp_hello: "@qq.com"
  smtp_require_tls: false
route: #设置报警的分发策略
  group_by: ["alertname"] #采用哪个标签来作为分组依据
  group_wait: 10s #组告警等待时间。也就是告警产生后等待10s,如果有同组告警一起发出
  group_interval: 10s #两组告警的间隔时间
  repeat_interval: 2m #重复告警的间隔时间,减少相同邮件的发送频率
  receiver: "web.hook" #设置接收人,这个接收人在下面会有定义
receivers:	# 定义接收人
  - name: "web.hook"	# 定义接收人:web.hook,这里定义为接收人是一个邮箱
    #webhook_configs:
    #- url: 'http://127.0.0.1:5001/'
    email_configs:
      - to: "2973707860@qq.com"
inhibit_rules: #禁止的规则
  - source_match: #源匹配级别
      severity: "critical"
    target_match:
      severity: "warning"
    equal: ["alertname", "dev", "instance"]

启动 alertmanager

# 二进制启动
$./alertmanager --config.file=./alertmanager.yml

# service启动
$vim /lib/systemd/system/alertmanager.service
[Unit]
Description=Prometheus Server
Documentation=https://prometheus.io/docs/introduction/overview/
After=network.target
[Service]
Restart=on-failure
WorkingDirectory=/usr/local/prometheus/
ExecStart=/usr/local/prometheus/prometheus --config.file=/usr/local/prometheus/prometheus.yml
[Install]
WantedBy=multi-user.target

$lsof -i:9093		# 监听9093端口
COMMAND     PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
alertmana 53151 root    9u  IPv6 195688      0t0  TCP *:9093 (LISTEN)

报警规则文件示例

groups:					
  - name:				# 警报规则组的名称
    rules:
      - alert:			# 警报规则的名称
        expr:			# 使用PromQL表达式完成的警报触发条件,用于计算是否有满足触发条件
        for:			# 触发报警条件,等一段时间(pending)再报警
        labels:			# 自定义标签,允许自行定义标签附加在警报上
        annotations:	# 设置有关警报的一组描述信息,其中包括自定义的标签,以及expr计算后的值
[root@k8s-master prometheus]$pwd
/usr/local/prometheus
[root@k8s-master prometheus]$vim rule.yml
groups:
  - name: linux_pod.rules
    rules:
      - alert: Pod_all_cpu_usage
        expr: (sum by(name)(rate(container_cpu_usage_seconds_total{image!=""}[5m]))*100) > 75
        for: 5m
        labels:
          severity: critical
          service: pods
        annotations:
          description: 容器 {{ $labels.name }} CPU 资源利用率大于 75% , (current value is {{ $value }})
          summary: Dev CPU 负载告警
      - alert: Pod_all_memory_usage
        expr: sort_desc(avg by(name)(irate(container_memory_usage_bytes{name!=""}[5m]))*100) > 1024*10^3*2
        for: 10m
        labels:
          severity: critical
        annotations:
          description: 容器 {{ $labels.name }} Memory 资源利用率大于 2G , (current value is {{ $value }})
          summary: Dev Memory 负载告警
      - alert: Pod_all_network_receive_usage
        expr: sum by (name)(irate(container_network_receive_bytes_total{container_name="POD"}[1m])) > 1024*1024*50
        for: 10m
        labels:
          severity: critical
        annotations:
          description: 容器 {{ $labels.name }} network_receive 资源利用率大于 50M , (current value is {{ $value }})

配置prometheus

$cat prometheus.yml 
...
# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - 10.0.1.31:9093 #alertmanager地址

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  - "/usr/local/prometheus/rule.yml" #指定规则文件

...

# 验证报警规则
[root@k8s-master prometheus]$./promtool check rules ./rule.yml
Checking ./rule.yml
  SUCCESS: 3 rules found
# 重启prometheus
[root@k8s-master prometheus]$systemctl restart prometheus

部署

kubectl apply -f nginx.yaml --record=true	# --record=true为记录执行的kubectl

滚动更新

滚动更新策略 spec.strategy

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: Enter deployment name
spec:
  strategy:
    rollingUpdate:
      maxSurge: 1			# 一次性最多添加多少pod
      maxUnavailable: 1		# 不可用Pod的数量最多是多少
    type: RollingUpdate		# 指定更新策略:滚动更新
  replicas: Enter the number of replicas
  template:
    metadata:
      labels:
        editor: vscode
    spec:
      containers:
      - name: name
        image: Enter containers image

更新策略有两种:

  • RollingUpdate :默认的更新策略,表示滚动更新
  • Recreate:重建,会终止所有正在运行的实例,然后用较新的版本来重新创建它们,即在创建新 Pods 之前,所有现有的 Pods 会被杀死,测试环境可以使用

Pod有多个副本,滚动更新的过程就是轮流更新pod,直至所有pod都更新成功,至于如何轮流更新,取决于 maxSurge 和 maxUnavailable 这两个参数,示例:

# 如果Pod有5个
maxSurge: 1			# 每次添加1个新版本Pod,启动成功后删除一个旧版本Pod
maxUnavailable: 0	# 不允许有不可用的Pod,即更新过程中始终保持5个Pod正常运行

升级命令 kubectl set

kubectl set image (-f FILENAME | TYPE NAME) CONTAINER_NAME_1=CONTAINER_IMAGE_1 ... CONTAINER_NAME_N=CONTAINER_IMAGE_N [options]

# 示例
kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1
kubectl set image deployments,rc nginx=nginx:1.9.1 --all
kubectl set image daemonset abc *=nginx:1.9.1
kubectl set image -f path/to/file.yaml nginx=nginx:1.9.1 --local -o yaml

除了使用 kubectl get 命令升级,还可以直接 kubectl apply -f 升级,但是需要先手动修改yaml文件,所以不推荐使用

回滚

回滚到上一个版本

kubectl rollout undo deployment/nginx-deployment -n linux36

回滚到指定版本

# 查看当前版本号
kubectl rollout history deployment/nginx-deployment -n linux36
deployment.extensions/nginx-deployment
REVISION CHANGE-CAUSE
1 	kubectl apply --filename=nginx.yaml --record=true
3 	kubectl apply --filename=nginx.yaml --record=true
4 	kubectl apply --filename=nginx.yaml --record=true

# 回滚到指定的1版本
kubectl rollout undo deployment/nginx-deployment --to-revision=1 -n linux36

Jenkins 持续继承与部署

参考以下脚本:

$cat build-command.sh

#!/bin/bash
docker build -t harbor.magedu.local/pub-images/nginx-base-wordpress:v1.14.2  .
sleep 1
docker push  harbor.magedu.local/pub-images/nginx-base-wordpress:v1.14.2
$cat linux36-ningx-deploy.sh

#!/bin/bash
#Author: ZhangShiJie
#Date: 2019-08-03
#Version: v1
#记录脚本开始执行时间
starttime=$(date +'%Y-%m-%d %H:%M:%S')
#变量
SHELL_DIR="/root/scripts"
SHELL_NAME="$0"
K8S_CONTROLLER1="192.168.7.101"
K8S_CONTROLLER2="192.168.7.102"
DATE=$(date +%Y-%m-%d_%H_%M_%S)
METHOD=$1
Branch=$2
if test -z $Branch; then
    Branch=develop
fi
function Code_Clone() {
    Git_URL="git@172.20.100.1:linux36/app1.git"
    DIR_NAME=$(echo ${Git_URL} | awk -F "/" '{print $2}' | awk -F "." '{print $1}')
    DATA_DIR="/data/gitdata/linux36"
    Git_Dir="${DATA_DIR}/${DIR_NAME}"
    cd ${DATA_DIR} && echo "即将清空上一版本代码并获取当前分支最新代码" && sleep 1 && rm -rf
    ${DIR_NAME}
    echo "即将开始从分支${Branch} 获取代码" && sleep 1
    git clone -b ${Branch} ${Git_URL}
    echo "分支${Branch} 克隆完成,即将进行代码编译!" && sleep 1
    #cd ${Git_Dir} && mvn clean package
    #echo "代码编译完成,即将开始将IP地址等信息替换为测试环境"
    #####################################################
    sleep 1
    cd ${Git_Dir}
    tar czf ${DIR_NAME}.tar.gz ./*
}

#将打包好的压缩文件拷贝到k8s 控制端服务器
function Copy_File() {
    echo "压缩文件打包完成,即将拷贝到k8s 控制端服务器${K8S_CONTROLLER1}" && sleep 1
    scp ${Git_Dir}/${DIR_NAME}.tar.gz root@${K8S_CONTROLLER1}:/opt/k8s-
    data/dockerfile/linux36/nginx/
    echo "压缩文件拷贝完成,服务器${K8S_CONTROLLER1}即将开始制作Docker 镜像!" && sleep 1
}

#到控制端执行脚本制作并上传镜像
function Make_Image() {
    echo "开始制作Docker镜像并上传到Harbor服务器" && sleep 1
    ssh root@${K8S_CONTROLLER1} "cd /opt/k8s-data/dockerfile/linux36/nginx && bash build-
command.sh ${DATE}"
    echo "Docker镜像制作完成并已经上传到harbor服务器" && sleep 1
}

#到控制端更新k8s yaml文件中的镜像版本号,从而保持yaml文件中的镜像版本号和k8s中版本号一致
function Update_k8s_yaml() {
    echo "即将更新k8s yaml文件中镜像版本" && sleep 1
    ssh root@${K8S_CONTROLLER1} "cd /opt/k8s-data/yaml/linux36/nginx && sed -i 's/image:
harbor.magedu.*/image: harbor.magedu.net\/linux36\/nginx-web1:${DATE}/g' nginx.yaml"
    echo "k8s yaml文件镜像版本更新完成,即将开始更新容器中镜像版本" && sleep 1
}

#到控制端更新k8s中容器的版本号,有两种更新办法,一是指定镜像版本更新,二是apply执行修改过的yaml文件
function Update_k8s_container() {
    #第一种方法
    ssh root@${K8S_CONTROLLER1} "kubectl set image deployment/linux36-nginx-deployment linux36-nginx-container=harbor.magedu.net/linux36/nginx-web1:${DATE} -n linux36"
    #第二种方法,推荐使用第一种
    #ssh root@${K8S_CONTROLLER1} "cd /opt/k8s-data/yaml/web-test/tomcat-app1 && kubectl apply -f web-test.yam --record"
    echo "k8s 镜像更新完成" && sleep 1
    echo "当前业务镜像版本: harbor.magedu.net/linux36/nginx-web1:${DATE}"
    #计算脚本累计执行时间,如果不需要的话可以去掉下面四行
    endtime=$(date +'%Y-%m-%d %H:%M:%S')
    start_seconds=$(date --date="$starttime" +%s)
    end_seconds=$(date --date="$endtime" +%s)
    echo "本次业务镜像更新总计耗时:"$((end_seconds - start_seconds))"s"
}

#基于k8s 内置版本管理回滚到上一个版本
function rollback_last_version() {
    echo "即将回滚之上一个版本"
    ssh root@${K8S_CONTROLLER1} "kubectl rollout undo deployment/linux36-nginx-deployment -n linux36"
    sleep 1
    echo "已执行回滚至上一个版本"
}

#使用帮助
usage() {
    echo "部署使用方法为 ${SHELL_DIR}/${SHELL_NAME} deploy "
    echo "回滚到上一版本使用方法为 ${SHELL_DIR}/${SHELL_NAME} rollback_last_version"
}

#主函数
main() {
    case ${METHOD} in
    deploy)
        Code_Clone
        Copy_File
        Make_Image
        Update_k8s_yaml
        Update_k8s_container
        ;;
    rollback_last_version)
        rollback_last_version
        ;;
    *)
        usage
        ;;
    esac
}

main $1 $2 $3

代码升级和回滚

代码升级:Jenkins负责pull最新代码,编译成war包(如果需要),然后将编译后的包发送到k8s或者专门负责制作镜像的服务器,使用dockerfile制作完新镜像后,就上传到harbor,最后再由kubectl执行更新镜像的操作

代码回滚:直接回滚镜像即可

Pod 的生命周期:https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/

Pod状态

  1. 第一阶段

    Pending		# 正在创建Pod但是Pod中的容器还没有全部被创建完成,处于此状态的Pod应该检查Pod依赖的存储是否有权限挂载、镜像是否可以下载、调度是否正常等
    Failed		# Pod中有容器启动失败而导致pod工作异常
    Unknown		# 由于某种原因无法获得pod的当前状态,通常是由于与pod所在的node节点通信错误
    Succeeded	# Pod中的所有容器都被成功终止即pod里所有的containers均已terminated
  2. 第二阶段

    Unschedulable		# Pod不能被调度,kube-scheduler没有匹配到合适的node节点
    PodScheduled		# pod正处于调度中,在kube-scheduler刚开始调度的时候,还没有将pod分配到指定的node,在筛选出合适的节点后就会更新etcd数据,将pod分配到指定的node
    Initialized			# 所有pod中的初始化容器已经完成了
    ImagePullBackOff	# Pod所在的node节点下载镜像失败
    Running				# Pod内部的容器已经被创建并且启动
    Ready				# 表示pod中的容器已经可以提供访问服务
  3. 第三阶段

    Succeeded
    Failed

Error                      #pod 启动过程中发生错误
NodeLost                   #Pod 所在节点失联
Unkown                     #Pod 所在节点失联或其它未知异常
Waiting                    #Pod 等待启动
Pending                    #Pod 等待被调度
Terminating:               #Pod 正在被销毁
CrashLoopBackOff           #pod,但是kubelet正在将它重启
InvalidImageName           #node节点无法解析镜像名称导致的镜像无法下载
ImageInspectError          #无法校验镜像,镜像不完整导致
ErrImageNeverPull          #策略禁止拉取镜像,镜像中心权限是私有等
ImagePullBackOff           #镜像拉取失败,但是正在重新拉取
RegistryUnavailable        #镜像服务器不可用,网络原因或harbor宕机
ErrImagePull               #镜像拉取出错,超时或下载被强制终止
CreateContainerConfigError #不能创建kubelet使用的容器配置
CreateContainerError       #创建容器失败
PreStartContainer          #执行preStart hook报错,Pod hook(钩子)是由 Kubernetes 管理的 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运行,比如容器创建完成后里面的服务启动之前可以检查一下依赖的其它服务是否启动,或者容器退出之前可以把容器中的服务先通过命令停止。
PostStartHookError         #执行 postStart hook 报错
RunContainerError          #pod运行失败,容器中没有初始化PID为1的守护进程等
ContainersNotInitialized   #pod没有初始化完毕
ContainersNotReady         #pod没有准备完毕
ContainerCreating          #pod正在创建中
PodInitializing            #pod正在初始化中
DockerDaemonNotReady       #node节点decker服务没有启动
NetworkPluginNotReady      #网络插件还没有完全启动

Pod调度过程

参考:kube-scheduler

Pod探针

https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/#container-probes

探针是 kubelet 对容器执行的定期诊断,以保证 Pod 的状态始终处于运行状态,要执行诊断,kubelet 调用由容器实现的 Handler(处理程序),有三种类型的处理程序:

ExecAction      #在容器内执行指定命令,如果命令退出时返回码为0则认为诊断成功
TCPSocketAction #对指定端口上的容器的IP地址进行TCP检查,如果端口打开,则诊断被认为是成功的
HTTPGetAction   #对指定的端口和路径上的容器的IP地址执行HTTPGet请求,如果响应的状态码大于等于200且小于 400,则诊断被认为是成功的

每次探测都将获得以下三种结果之一:

成功:容器通过了诊断
失败:容器未通过诊断
未知:诊断失败,因此不会采取任何行动

探针类型

livenessProbe	# 存活探针,检测容器容器是否正在运行,如果存活探测失败,则kubelet会杀死容器,并且容器将受到其重启策略的影响,如果容器不提供存活探针,则默认状态为 Success,livenessProbe用于控制是否重启pod

readinessProbe	# 就绪探针,如果就绪探测失败,端点控制器将从与Pod匹配的所有Service的端点中删除该Pod的IP地址,初始延迟之前的就绪状态默认为Failure(失败),如果容器不提供就绪探针,则默认状态为 Success,readinessProbe用于控制pod是否添加至service

livenessProbe和readinessProbe的对比:

1. 配置参数一样
2. livenessProbe用于控制是否重启pod,readinessProbe用于控制pod是否添加至service
3. livenessProbe连续探测失败会重启、重建pod,readinessProbe不会执行重启或者重建Pod操作
4. livenessProbe连续检测指定次数失败后会将容器置于(Crash Loop BackOff)且不可用,readinessProbe不会
5. readinessProbe 连续探测失败会从service的endpointd中删除该Pod,livenessProbe不具备此功能,但是会将容器挂起livenessProbe

建议:两个探针都配置

探针配置

探针有很多配置字段,可以使用这些字段精确的控制存活和就绪检测的行为:

initialDelaySeconds: 120 # 初始化延迟时间,告诉kubelet在执行第一次探测前应该等待多少秒,默认是0秒,最小值是0
periodSeconds: 60        # 探测周期间隔时间,指定了kubelet应该每多少秒秒执行一次存活探测,默认是10秒。最小值是1
timeoutSeconds: 5        # 单次探测超时时间,探测的超时后等待多少秒,默认值是1秒,最小值是1
successThreshold: 1      # 从失败转为成功的重试次数,探测器在失败后,被视为成功的最小连续成功数,默认值是1,存活探测的这个值必须是1,最小值是 1
failureThreshold: 3      # 从成功转为失败的重试次数,当Pod启动了并且探测到失败,Kubernetes的重试次数,存活探测情况下的放弃就意味着重新启动容器,就绪探测情况下的放弃Pod 会被打上未就绪的标签,默认值是3,最小值是1

HTTP 探测器可以在 httpGet 上配置额外的字段:

host:                     #连接使用的主机名,默认是Pod的 IP,也可以在HTTP头中设置 "Host" 来代替
scheme: http              #用于设置连接主机的方式(HTTP 还是 HTTPS),默认是 HTTP
path: /monitor/index.html #访问 HTTP 服务的路径
httpHeaders:              #请求中自定义的 HTTP 头,HTTP 头字段允许重复
port: 80                  #访问容器的端口号或者端口名,如果数字必须在 1 ~ 65535 之间

HTTP探针示例

#apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels: #rs or deployment
      app: ng-deploy-80
    #matchExpressions:
    # - {key: app, operator: In, values: [ng-deploy-80,ng-rs-81]}
    template:
      metadata:
        labels:
          app: ng-deploy-80
      spec:
        containers:
        - name: ng-deploy-80
          image: nginx:1.17.5
          ports:
          - containerPort: 80
          #readinessProbe:
          livenessProbe:
            httpGet:
              #path: /monitor/monitor.html
              path: /index.html       # 探测的url
              port: 80
            initialDelaySeconds: 5    # 等五秒才开始第一次检测
            periodSeconds: 3          # 探测周期
            timeoutSeconds: 5         # 单次探测超时时间
            successThreshold: 1       # 处于失败中,探测成功一次就算成功
            failureThreshold: 3       # 出于成功中,探测失败三次才算失败
---
apiVersion: v1
kind: Service
metadata:
  name: ng-deploy-80
spec:
ports:
- name: http
  port: 81
  targetPort: 80
  nodePort: 40012
  protocol: TCP
type: NodePort
selector:
  app: ng-deploy-80

TCP探针示例

#apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
  matchLabels: #rs or deployment
    app: ng-deploy-80
  #matchExpressions:
  # - {key: app, operator: In, values: [ng-deploy-80,ng-rs-81]}
  template:
    metadata:
      labels:
        app: ng-deploy-80
    spec:
      containers:
        - name: ng-deploy-80
          image: nginx:1.17.5
          ports:
            - containerPort: 80
          livenessProbe: #或readinessProbe:
            tcpSocket:
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 3
            timeoutSeconds: 5
            successThreshold: 1
            failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
  name: ng-deploy-80
spec:
  ports:
    - name: http
      port: 81
      targetPort: 80
      nodePort: 40012
      protocol: TCP
  type: NodePort
  selector:
    app: ng-deploy-80

ExecAction探针

基于指定的命令对Pod进行特定的状态检查

#apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
spec:
  replicas: 1
  selector:
  matchLabels: #rs or deployment
    app: redis-deploy-6379
  #matchExpressions:
  # - {key: app, operator: In, values: [redis-deploy-6379,ng-rs-81]}
  template:
    metadata:
      labels:
        app: redis-deploy-6379
    spec:
      containers:
        - name: redis-deploy-6379
      image: redis
      ports:
        - containerPort: 6379
      #readinessProbe:
      livenessProbe:
        exec:
          command:		# 基于指定的命令对Pod进行特定的状态检查
            #- /apps/redis/bin/redis-cli
            - /usr/local/bin/redis-cli
            - quit
        initialDelaySeconds: 5
        periodSeconds: 3
        timeoutSeconds: 5
        successThreshold: 1
        failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
  name: redis-deploy-6379
spec:
  type: NodePort
  ports:
    - name: http
      port: 6379
      targetPort: 6379
      nodePort: 40016
      protocol: TCP
  selector:
    app: redis-deploy-6379

Pod重启策略

k8s在Pod出现异常的时候会自动将Pod重启,以恢复Pod中的服务

restartPolicy:
	Always	# 当容器异常时,k8s自动重启该容器,ReplicationController/Replicaset/Deployment
	OnFailure	# 当容器失败时(容器停止运行且退出码不为0),k8s自动重启该容器
	Never	# 不论容器运行状态如何都不会重启该容器,Job或CronJob

镜像拉取策略

配置最佳实践 | Kubernetes

imagePullPolicy: 
	IfNotPresent	# node节点没有此镜像就去指定的镜像仓库拉取,node有就使用node本地镜像
	Always		# 每次重建pod都会重新拉取镜像
	Never		# 从不到镜像中心拉取镜像,只使用本地镜像

HPA:Horizontal Pod Autoscaler,pod的自动水平伸缩,特别适合无状态服务

HPA 会自动完成pod的扩缩容,当资源需求过高时,会自动创建出pod副本;当资源需求低时,会自动收缩pod副本数。

注意:通过集群内的资源监控系统(metrics-server),来获取集群中资源的使用状态,所以必须确保集群中已经安装metrics-server的组件

HPA 版本:

$kubectl api-versions | grep auto
autoscaling/v1			# 只支持通过cpu为参考依据,来改变pod副本数
autoscaling/v2beta1		# 支持通过cpu、内存、连接数以及用户自定义的资源指标数据为参考依据
autoscaling/v2beta2		# 同上,小的变动

metrics-server

概念

Metrics Server 是 Kubernetes 集群核心监控数据的聚合器,Metrics Server 从 Kubelet 收集资源指标,并通过 Merics API 在 Kubernetes APIServer 中提供给缩放资源对象 HPA 使用。也可以通过 Metrics API 提供的 Kubectl top 查看 Pod 资源占用情况,从而实现对资源的自动缩放。

Metrics Server 是 Kubernetes 监控组件中的重要一部分,Metrics Server 主要分为 API 和 Server 两大部分。

  • Metrics API:通过 APIServer 对外暴露 Pod 资源使用情况。为 HPA、kubectl top、Kubernetes dashboard 等提供数据来源
  • Metrics Server :定期通过 Summary API 从 Kubelet 所在集群节点获取服务指标,然后将指标汇总、存储到内存中,仅仅存储指标最新状态,一旦重启组件数据将会丢失

部署

最新的 metrics-server 是 v4.0.2,但是其依赖的镜像需要翻墙,而阿里云镜像和 mirrorgooglecontainers 中都没有最新版本,最新的只有 v0.3.6 版本,勉强用吧

$docker pull registry.aliyuncs.com/google_containers/metrics-server-amd64:v0.3.6
$docker tag registry.aliyuncs.com/google_containers/metrics-server-amd64 harbor.ljk.local/k8s/metrics-server-amd64:v0.3.6
$docker push harbor.ljk.local/k8s/metrics-server-amd64:v0.3.6

# 下载 components.yaml
$wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.6/components.yaml
$vim components.yaml
...
image: harbor.ljk.local/k8s/metrics-server-amd64:v0.3.6	 #修改为本地镜像
...
# 安装
$kubectl apply -f components.yaml

# 测试
$nohup kubectl proxy &
$curl http://localhost:8001/apis/metrics.k8s.io/v1beta1/nodes
$curl http://localhost:8001/apis/metrics.k8s.io/v1beta1/pods
$kubectl top node
$kubectl top pod -A

HPA示例

要求:我有个deployment叫myapp现在只有一个副本数,最多只能8个副本数,当pod的cpu平均利用率超过百分之50或内存平均值超过百分之50时,pod将自动增加副本数以提供服务

  1. SVC、Deployment资源清单:

    apiVersion: v1
    kind: Service
    metadata:
      name: svc-hpa
      namespace: default
    spec:
      selector:
        app: myapp
      type: NodePort  ##注意这里是NodePort,下面压力测试要用到。
      ports:
      - name: http
        port: 80
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: myapp
      namespace: default
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: myapp
      template:
        metadata:
          name: myapp-demo
          namespace: default
          labels:
            app: myapp
        spec:
          containers:
          - name: myapp
            image: ikubernetes/myapp:v1
            imagePullPolicy: IfNotPresent
            ports:
            - name: http
              containerPort: 80
            resources:
              requests:
                cpu: 50m
                memory: 50Mi
              limits:
                cpu: 50m
                memory: 50Mi
  2. HPA资源清单:

    apiVersion: autoscaling/v2beta1
    kind: HorizontalPodAutoscaler
    metadata:
      name: myapp-hpa-v2
      namespace: default
    spec:
      minReplicas: 1         ##至少1个副本
      maxReplicas: 8         ##最多8个副本
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: myapp
      metrics:
      - type: Resource
        resource:
          name: cpu
          targetAverageUtilization: 50  ##注意此时是根据使用率,也可以根据使用量:targetAverageValue
      - type: Resource
        resource:
          name: memory
          targetAverageUtilization: 50  ##注意此时是根据使用率,也可以根据使用量:targetAverageValue

安装

npm install json -g

使用

# 标准输入
<something generating JSON on stdout> | json [OPTIONS] [LOOKUPS...]

# 从文件中加载
json -f FILE [OPTIONS] [LOOKUPS...]
  • -e:修改

    $ echo '{"name":"trent","age":38}' | json -e 'this.age++'
    {
    "name": "trent",
    "age": 39
    }
    
    $json -I -f tmp.json -e 'this.deploy.type="git"'
  • -c:添加

    json -I -f tmp.json -c 'this.deploy.branch="main"'

资源清单是yml格式,api-server会自动转成json格式

每个API对象都有3大类属性:元数据metadata、规范spec和状态status

spec 是期望状态,status是实际状态,如果实际状态和期望状态有出入,控制器(通常是deployment控制器)的控制循环就会监控到差异,然后将需要做出的更改提交到apiserver,调度器scheduler监控到apiserver中有未执行的操作,就会去找适合执行操作的node,然后提交到apiserver,kubelet监控到apiserver中有关于自己节点的操作,就会执行操作,将执行结果返回给apiserver,apiserver再更新实际状态

deployment

HorizontalPodAutoscaler

kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta1
metadata:
  namespace: linux42
  name: tomcat-app1-podautoscaler
  labels:
    app: tomcat-app1 #自定义app标签
    version: v2beta1 #自定义version标签
spec: # 对象具体信息
  scaleTargetRef: #定义水平伸缩的目标对象:Deployment、ReplicationController/ReplicaSet
    kind: Deployment #目标对象类型为deployment
    apiVersion: apps/v1 #API版本
    name: tomcat-app1-deployment #deployment的名称
  minReplicas: 2 #最小pod数
  maxReplicas: 5 #最大pod数
  metrics: #需要安装metrics server
    - type: Resource # 类型为资源
      resource: # 定义资源
        name: cpu
        targetAverageUtilization: 80 #CPU使用率,超过80%就增加pod
    - type: Resource
      resource:
        name: memory
        targetAverageValue: 1024Mi # 内存使用量,超过1024Mi就增加pod

namespace

apiVersion: v1
kind: Namespace
metadata:
  name: test

Nginx 业务yaml文件

kind: Deployment
apiVersion: extensions/v1beta1 # API版本
metadata: # deployment元数据
  name: nginx-deployment # deployment名称,创建后的pdo名称是这个名称加随机字符串
  namespace: test # pod的namespace,默认是default
  labels: # 自定义deployment标签
    app: nginx-deployment-label
spec: # deployment的详细信息
  replicas: 1 # 创建出的pod的副本数,即多少个pod,默认值为1
  selector: # Deployment如何查找要管理的Pods,它必须与pod模板的标签相匹配
    matchLabels: # 定义匹配Pod的标签,pod模板的标签相匹配
      app: nginx-selector # 就是下面的 template.metadata.labels.app
  template: # 定义模板,必须定义,用于描述要创建的pod
    metadata: # pod元数据
      labels: # 自定义pod的标签,主要用于service匹配
        app: nginx-selector # 自定义app标签
    spec: # pod详细信息
      containers: # pod中容器列表,可以多个,至少一个,绝大部分情况是一个,pod不能动态增减容器
        - name: nginx-container # 容器名称
          image:
            harbor.magedu.net/test/nginx-web1:v1 # 镜像地址
            # command: ["/apps/tomcat/bin/run_tomcat.sh"]     # 容器启动执行的命令或脚本
            # imagePullPolicy: IfNotPresent
          imagePullPolicy: Always # 拉取镜像策略
          ports: # 定义容器端口列表
            - name: http # 端口名称
              containerPort: 80
              protocol: TCP
            - name: https # 端口名称
              containerPort: 443
              protocol: TCP
          env: # 配置环境变量
            - name: "password" # 变量名称。必须要用引号引起来
              value: "123456"
            - name: "age"
              value: "18"
          resources: # 对资源的请求设置和限制设置
            limits: # 资源限制设置上限
              cpu: 2 # cpu的限制,单位为core数,可以写0.5或者500m等CPU压缩值
              memory: 2Gi # 内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
            requests: # 资源请求的设置
              cpu: 1 # cpu请求数,容器启动的初始可用数量,可以写0.5或者500m等CPU压缩值
              memory: 512Mi # 内存请求大小,容器启动的初始可用数量,用于调度pod时候使用
---
kind: Service
apiVersion: v1 # API版本
metadata: # service元数据
  name: nginx-spec # service的名称,此名称会被DNS解析
  namespace: test # 该service隶属于的namespaces名称,即把service创建到哪个namespace里面
  labels: # 自定义service标签
    app: nginx
spec: # service的详细信息
  type: NodePort # service的类型,定义服务的访问方式,默认为ClusterIP
  ports: # 定义访问端口
    - name: http # 定义一个端口名称
      port: 80 # service 80端口
      protocol: TCP # 协议类型
      targetPort: 80 # 目标pod的端口
      nodePort: 30001 # node节点暴露的端口
    - name: https
      port: 443
      protocol: TCP
      targetPort: 443
      nodePort: 30043
  selector: # service的标签选择器,匹配Pod的标签
    app: nginx-selector # 匹配定义了app标签且值为nginx-selector的Pod

Master PDF Editor,强大的多功能PDF编辑器,轻松查看,创建,修改,批注,签名,扫描,OCR和打印PDF文档。高级注释工具,可以添加任意便笺指示对象突出显示,加下划线和删除,而无需更改源PDF文件

添加目录

左键选中文字,右键添加书签

批量去除水印

VXLAN

vxlan是属于overlay中的一种隧道封装技术,实际上将L2(数据链路层)的以太网帧封装成L4(传输层)的UDP数据报文,然后在L3网络层传输,最终实现的效果就类似于在L2的以太网传输报文一样,但是不受L3网络层限制

为什么是在L3传输?
因为传输是基于路由表,路由只涉及ip,不涉及端口

vxlan标志位有24位,可以划分2^24个虚拟局域网

1. 感觉封装UDP是多余的,只靠IP也能通信
因为Vxlan的封装思维是将原始数据报文当做用户数据包,VTEP当做大二层接入,那么VTEP会依次进行传输层封装,网络层封装,以太网头部封装,如果直接进行IP封装则跳过了传输层的封装过程,会在传输的过程中遇到一些困难,很多数据中心里都会有大量的冗余链路,交换机面对多条等价路径时会进行基于五元组进行HASH,此时会出现问题;其次,在遇到NAT设备时,无法穿透也会造成影响

2. 为什么封装成UDP,而不是TCP
因为封装UDP开销比较小,至于TCP比UDP更可靠,但是依靠原始数据报文的TCP也可以实现可靠性的要求

网络模型

docker的网络模型

  • Bridge:桥接
  • Host:主机
  • Container:容器

kubernetes网络模型

kubernetes的网络模型主要用于解决四类通信需求

Pod内通信

命名空间:linux底层概念,在内核层实现,容器使用namespce技术实现容器间相互隔离

  • mnt namespace:隔离根,即chroot技术
  • ipc namespace:隔离进程间通信
  • uts namespace:隔离系统信息,主机名等信息
  • pid namespace:隔离进程号
  • net namespace:隔离网络
  • user namespce:隔离用户

运行在同一个pod内的容器,共享net、ipc、uts、一组存储卷,亲密关系,同进同退

Pod间通信 ★★★

网络模型:所有Pod在一个平面网络中,每个Pod拥有一个集群全局唯一的地址,可直接与其他Pod通信

k8s设计了网络模型,但将其实现交给网络插件,主流的网络插件有:flannel、calico、kube-router 等

flannel

早期版本的Flannel使用UDP封装完成报文的跨越主机转发,其安全性及性能略有不足,现在已经废弃这种方式了,现在只能通过 vxlan 或 host-gw 进行通信

  • flannel VXLAN后端:

  • flannel VXLAN Direct Routing后端:

    Directrouting 为在同一个二层网络中的node节点启用直接路由机制,类似于host-gw模式

  • host-gw后端:

    类似 calico,通过路由表完成报文转发

calico

Calico本身是一个三层的虚拟网络方案,它将每个节点都当作路由器(router),将每个节点的容器都当作是“节点路由器”的一个终端并为其分配一个IP地址,各节点路由器通过BGP(BorderGateway Protocol)学习生成路由规则,从而将不同节点上的容器连接起来

BGP是互联网上一个核心的去中心化自治路由协议,性能非常高,需要物理路由器的支持,这也是公有云不提供calico的原因,因为公有云上节点连接的都是虚拟路由器

calico 核心组件:

  • Felix:calico的agent,维护路由规则、汇报当前节点状态
  • Etcd:路由规则储存在etcd
  • BGP Client:运行在每个node上,负责监听由felix生成的路由信息,然后通过BGP协议广播至其他node节点,其他node的Felix会自动更新路由规则,从而实现路由的相互学习
  • Route Reflector:集中式的路由反射器,维护全网的路由规则,替换路由广播,如果节点非常多,可以考虑使用

BGP模式下calico的通信过程:

# traceroute命令查看请求ip为10.10.74.193的pod
/ $ traceroute 10.10.74.193
traceroute to 10.10.74.193 (10.10.74.193), 30 hops max, 46 byte packets
 1  10.0.1.32 (10.0.1.32)  0.005 ms  0.006 ms  0.002 ms		# 当前pod所在节点的ip
 2  10.0.1.33 (10.0.1.33)  1.220 ms  0.254 ms  0.147 ms		# 对方pod所在节点的ip
 3  10.10.74.193 (10.10.74.193)  0.207 ms  0.308 ms  0.163 ms	# 对方pod的ip

如果node需要跨网段通信,Calico也提供了IPIP模式,IPIP模式下,calico会在每个节点上创建一个tunl0接口(TUN类型虚拟设备)用于封装三层隧道报文。节点上每创建一个Pod资源,都自动创建一对虚拟以太网接口(TAP类型的虚拟设备),其中一个附加于Pod的网络名称空间,另一个(名称以cali为前缀后跟随机字串)留置在节点的根网络名称空间,并经由tunl0封装或解封三层隧道报文,Calico IPIP 模式如下图:

IPIP模式下calico的通信过程:

/ $ traceroute 10.10.58.65		# 请求ip为10.10.58.65的pod
traceroute to 10.10.58.65 (10.10.58.65), 30 hops max, 46 byte packets
 1  172.31.6.210 (172.31.6.210)  0.004 ms  0.004 ms  0.002 ms	# 当前pod所在node地址
 2  10.10.58.64 (10.10.58.64)  0.003 ms  0.432 ms  0.497 ms	# 对方pod所在node的tunl0地址
 3  10.10.58.65 (10.10.58.65)  0.553 ms  2.114 ms  0.775 ms	# 对方pod地址

BGP模式则直接使用物理机作为虚拟路由路(vRouter),请求直接通过路由跳转,不再创建额外的tunnel,没有tunnel封装、解封的步骤,所以BGP的性能会比IPIP高很多,建议禁用IPIP

Service与Pod通信

集群外部与Service通信

请求流量首先到达外部负载均衡器,由其调度至某个工作节点之上,而后再由工作节点的netfilter(kube-proxy)组件上的规则(iptables或ipvs)调度至某个目标Pod对象

集群外的流量先进入节点网络,再进入service网络,最后进入pod网络

网络策略

网络策略(Network Policy)是用于控制分组的Pod资源彼此之间如何进行通信,以及分组的Pod资源如何与其他网络端点进行通信的规范。它用于为Kubernetes实现更为精细的流量控制,实现租户隔离机制。Kubernetes使用标准的资源对象“NetworkPolicy”供管理员按需定义网络访问控制策略

Kubernetes的网络策略功能由其所使用的网络插件实现,Calico、Canal及kube-router支持网络策略,而flannel不支持

etcdctl is a command line client for etcd.

The v3 API is used by default on master branch. For the v2 API, make sure to set environment variable ETCDCTL_API=2. See also READMEv2.

If using released versions earlier than v3.4, set ETCDCTL_API=3 to use v3 API.

Global flags (e.g., dial-timeout, --cacert, --cert, --key) can be set with environment variables:

ETCDCTL_DIAL_TIMEOUT=3s
ETCDCTL_CACERT=/tmp/ca.pem
ETCDCTL_CERT=/tmp/cert.pem
ETCDCTL_KEY=/tmp/key.pem

Prefix flag strings with ETCDCTL_, convert all letters to upper-case, and replace dash(-) with underscore(_). Note that the environment variables with the prefix ETCDCTL_ can only be used with the etcdctl global flags. Also, the environment variable ETCDCTL_API is a special case variable for etcdctl internal use only.

Key-value commands

PUT [options] <key> <value>

PUT assigns the specified value with the specified key. If key already holds a value, it is overwritten.

RPC: Put

Options

  • lease – lease ID (in hexadecimal) to attach to the key.

  • prev-kv – return the previous key-value pair before modification.

  • ignore-value – updates the key using its current value.

  • ignore-lease – updates the key using its current lease.

Output

OK

Examples

./etcdctl put foo bar --lease=1234abcd
# OK
./etcdctl get foo
# foo
# bar
./etcdctl put foo --ignore-value # to detache lease
# OK
./etcdctl put foo bar --lease=1234abcd
# OK
./etcdctl put foo bar1 --ignore-lease # to use existing lease 1234abcd
# OK
./etcdctl get foo
# foo
# bar1
./etcdctl put foo bar1 --prev-kv
# OK
# foo
# bar
./etcdctl get foo
# foo
# bar1

Remarks

If <value> isn’t given as command line argument, this command tries to read the value from standard input.

When <value> begins with ‘-‘, <value> is interpreted as a flag.
Insert ‘–’ for workaround:

./etcdctl put <key> -- <value>
./etcdctl put -- <key> <value>

Providing <value> in a new line after using carriage return is not supported and etcdctl may hang in that case. For example, following case is not supported:

./etcdctl put <key>\r
<value>

A <value> can have multiple lines or spaces but it must be provided with a double-quote as demonstrated below:

./etcdctl put foo "bar1 2 3"

GET [options] <key> [range_end]

GET gets the key or a range of keys [key, range_end) if range_end is given.

RPC: Range

Options

  • hex – print out key and value as hex encode string

  • limit – maximum number of results

  • prefix – get keys by matching prefix

  • order – order of results; ASCEND or DESCEND

  • sort-by – sort target; CREATE, KEY, MODIFY, VALUE, or VERSION

  • rev – specify the kv revision

  • print-value-only – print only value when used with write-out=simple

  • consistency – Linearizable(l) or Serializable(s)

  • from-key – Get keys that are greater than or equal to the given key using byte compare

  • keys-only – Get only the keys

Output

<key>\n<value>\n<next_key>\n<next_value>…

Examples

First, populate etcd with some keys:

./etcdctl put foo bar
# OK
./etcdctl put foo1 bar1
# OK
./etcdctl put foo2 bar2
# OK
./etcdctl put foo3 bar3
# OK

Get the key named foo:

./etcdctl get foo
# foo
# bar

Get all keys:

./etcdctl get --from-key ''
# foo
# bar
# foo1
# bar1
# foo2
# foo2
# foo3
# bar3

Get all keys with names greater than or equal to foo1:

./etcdctl get --from-key foo1
# foo1
# bar1
# foo2
# bar2
# foo3
# bar3

Get keys with names greater than or equal to foo1 and less than foo3:

./etcdctl get foo1 foo3
# foo1
# bar1
# foo2
# bar2

Remarks

If any key or value contains non-printable characters or control characters, simple formatted output can be ambiguous due to new lines. To resolve this issue, set --hex to hex encode all strings.

DEL [options] <key> [range_end]

Removes the specified key or range of keys [key, range_end) if range_end is given.

RPC: DeleteRange

Options

  • prefix – delete keys by matching prefix

  • prev-kv – return deleted key-value pairs

  • from-key – delete keys that are greater than or equal to the given key using byte compare

Output

Prints the number of keys that were removed in decimal if DEL succeeded.

Examples

./etcdctl put foo bar
# OK
./etcdctl del foo
# 1
./etcdctl get foo
./etcdctl put key val
# OK
./etcdctl del --prev-kv key
# 1
# key
# val
./etcdctl get key
./etcdctl put a 123
# OK
./etcdctl put b 456
# OK
./etcdctl put z 789
# OK
./etcdctl del --from-key a
# 3
./etcdctl get --from-key a
./etcdctl put zoo val
# OK
./etcdctl put zoo1 val1
# OK
./etcdctl put zoo2 val2
# OK
./etcdctl del --prefix zoo
# 3
./etcdctl get zoo2

TXN [options]

TXN reads multiple etcd requests from standard input and applies them as a single atomic transaction.
A transaction consists of list of conditions, a list of requests to apply if all the conditions are true, and a list of requests to apply if any condition is false.

RPC: Txn

Options

  • hex – print out keys and values as hex encoded strings.

  • interactive – input transaction with interactive prompting.

Input Format

<Txn> ::= <CMP>* "\n" <THEN> "\n" <ELSE> "\n"
<CMP> ::= (<CMPCREATE>|<CMPMOD>|<CMPVAL>|<CMPVER>|<CMPLEASE>) "\n"
<CMPOP> ::= "<" | "=" | ">"
<CMPCREATE> := ("c"|"create")"("<KEY>")" <CMPOP> <REVISION>
<CMPMOD> ::= ("m"|"mod")"("<KEY>")" <CMPOP> <REVISION>
<CMPVAL> ::= ("val"|"value")"("<KEY>")" <CMPOP> <VALUE>
<CMPVER> ::= ("ver"|"version")"("<KEY>")" <CMPOP> <VERSION>
<CMPLEASE> ::= "lease("<KEY>")" <CMPOP> <LEASE>
<THEN> ::= <OP>*
<ELSE> ::= <OP>*
<OP> ::= ((see put, get, del etcdctl command syntax)) "\n"
<KEY> ::= (%q formatted string)
<VALUE> ::= (%q formatted string)
<REVISION> ::= "\""[0-9]+"\""
<VERSION> ::= "\""[0-9]+"\""
<LEASE> ::= "\""[0-9]+\""

Output

SUCCESS if etcd processed the transaction success list, FAILURE if etcd processed the transaction failure list. Prints the output for each command in the executed request list, each separated by a blank line.

Examples

txn in interactive mode:

./etcdctl txn -i
# compares:
mod("key1") > "0"

# success requests (get, put, delete):
put key1 "overwrote-key1"

# failure requests (get, put, delete):
put key1 "created-key1"
put key2 "some extra key"

# FAILURE

# OK

# OK

txn in non-interactive mode:

./etcdctl txn <<<'mod("key1") > "0"

put key1 "overwrote-key1"

put key1 "created-key1"
put key2 "some extra key"

'

# FAILURE

# OK

# OK

Remarks

When using multi-line values within a TXN command, newlines must be represented as \n. Literal newlines will cause parsing failures. This differs from other commands (such as PUT) where the shell will convert literal newlines for us. For example:

./etcdctl txn <<<'mod("key1") > "0"

put key1 "overwrote-key1"

put key1 "created-key1"
put key2 "this is\na multi-line\nvalue"

'

# FAILURE

# OK

# OK

COMPACTION [options] <revision>

COMPACTION discards all etcd event history prior to a given revision. Since etcd uses a multiversion concurrency control
model, it preserves all key updates as event history. When the event history up to some revision is no longer needed,
all superseded keys may be compacted away to reclaim storage space in the etcd backend database.

RPC: Compact

Options

  • physical – ‘true’ to wait for compaction to physically remove all old revisions

Output

Prints the compacted revision.

Example

./etcdctl compaction 1234
# compacted revision 1234

WATCH [options] [key or prefix] [range_end] [–] [exec-command arg1 arg2 …]

Watch watches events stream on keys or prefixes, [key or prefix, range_end) if range_end is given. The watch command runs until it encounters an error or is terminated by the user. If range_end is given, it must be lexicographically greater than key or “\x00”.

RPC: Watch

Options

  • hex – print out key and value as hex encode string

  • interactive – begins an interactive watch session

  • prefix – watch on a prefix if prefix is set.

  • prev-kv – get the previous key-value pair before the event happens.

  • rev – the revision to start watching. Specifying a revision is useful for observing past events.

Input format

Input is only accepted for interactive mode.

watch [options] <key or prefix>\n

Output

<event>[\n<old_key>\n<old_value>]\n<key>\n<value>\n<event>\n<next_key>\n<next_value>\n…

Examples

Non-interactive
./etcdctl watch foo
# PUT
# foo
# bar
ETCDCTL_WATCH_KEY=foo ./etcdctl watch
# PUT
# foo
# bar

Receive events and execute echo watch event received:

./etcdctl watch foo -- echo watch event received
# PUT
# foo
# bar
# watch event received

Watch response is set via ETCD_WATCH_* environmental variables:

./etcdctl watch foo -- sh -c "env | grep ETCD_WATCH_"

# PUT
# foo
# bar
# ETCD_WATCH_REVISION=11
# ETCD_WATCH_KEY="foo"
# ETCD_WATCH_EVENT_TYPE="PUT"
# ETCD_WATCH_VALUE="bar"

Watch with environmental variables and execute echo watch event received:

export ETCDCTL_WATCH_KEY=foo
./etcdctl watch -- echo watch event received
# PUT
# foo
# bar
# watch event received
export ETCDCTL_WATCH_KEY=foo
export ETCDCTL_WATCH_RANGE_END=foox
./etcdctl watch -- echo watch event received
# PUT
# fob
# bar
# watch event received
Interactive
./etcdctl watch -i
watch foo
watch foo
# PUT
# foo
# bar
# PUT
# foo
# bar

Receive events and execute echo watch event received:

./etcdctl watch -i
watch foo -- echo watch event received
# PUT
# foo
# bar
# watch event received

Watch with environmental variables and execute echo watch event received:

export ETCDCTL_WATCH_KEY=foo
./etcdctl watch -i
watch -- echo watch event received
# PUT
# foo
# bar
# watch event received
export ETCDCTL_WATCH_KEY=foo
export ETCDCTL_WATCH_RANGE_END=foox
./etcdctl watch -i
watch -- echo watch event received
# PUT
# fob
# bar
# watch event received

LEASE <subcommand>

LEASE provides commands for key lease management.

LEASE GRANT <ttl>

LEASE GRANT creates a fresh lease with a server-selected time-to-live in seconds
greater than or equal to the requested TTL value.

RPC: LeaseGrant

Output

Prints a message with the granted lease ID.

Example

./etcdctl lease grant 60
# lease 32695410dcc0ca06 granted with TTL(60s)

LEASE REVOKE <leaseID>

LEASE REVOKE destroys a given lease, deleting all attached keys.

RPC: LeaseRevoke

Output

Prints a message indicating the lease is revoked.

Example

./etcdctl lease revoke 32695410dcc0ca06
# lease 32695410dcc0ca06 revoked

LEASE TIMETOLIVE <leaseID> [options]

LEASE TIMETOLIVE retrieves the lease information with the given lease ID.

RPC: LeaseTimeToLive

Options

  • keys – Get keys attached to this lease

Output

Prints lease information.

Example

./etcdctl lease grant 500
# lease 2d8257079fa1bc0c granted with TTL(500s)

./etcdctl put foo1 bar --lease=2d8257079fa1bc0c
# OK

./etcdctl put foo2 bar --lease=2d8257079fa1bc0c
# OK

./etcdctl lease timetolive 2d8257079fa1bc0c
# lease 2d8257079fa1bc0c granted with TTL(500s), remaining(481s)

./etcdctl lease timetolive 2d8257079fa1bc0c --keys
# lease 2d8257079fa1bc0c granted with TTL(500s), remaining(472s), attached keys([foo2 foo1])

./etcdctl lease timetolive 2d8257079fa1bc0c --write-out=json
# {"cluster_id":17186838941855831277,"member_id":4845372305070271874,"revision":3,"raft_term":2,"id":3279279168933706764,"ttl":465,"granted-ttl":500,"keys":null}

./etcdctl lease timetolive 2d8257079fa1bc0c --write-out=json --keys
# {"cluster_id":17186838941855831277,"member_id":4845372305070271874,"revision":3,"raft_term":2,"id":3279279168933706764,"ttl":459,"granted-ttl":500,"keys":["Zm9vMQ==","Zm9vMg=="]}

./etcdctl lease timetolive 2d8257079fa1bc0c
# lease 2d8257079fa1bc0c already expired

LEASE LIST

LEASE LIST lists all active leases.

RPC: LeaseLeases

Output

Prints a message with a list of active leases.

Example

./etcdctl lease grant 60
# lease 32695410dcc0ca06 granted with TTL(60s)

./etcdctl lease list
32695410dcc0ca06

LEASE KEEP-ALIVE <leaseID>

LEASE KEEP-ALIVE periodically refreshes a lease so it does not expire.

RPC: LeaseKeepAlive

Output

Prints a message for every keep alive sent or prints a message indicating the lease is gone.

Example

./etcdctl lease keep-alive 32695410dcc0ca0
# lease 32695410dcc0ca0 keepalived with TTL(100)
# lease 32695410dcc0ca0 keepalived with TTL(100)
# lease 32695410dcc0ca0 keepalived with TTL(100)
...

Cluster maintenance commands

MEMBER <subcommand>

MEMBER provides commands for managing etcd cluster membership.

MEMBER ADD <memberName> [options]

MEMBER ADD introduces a new member into the etcd cluster as a new peer.

RPC: MemberAdd

Options

  • peer-urls – comma separated list of URLs to associate with the new member.

Output

Prints the member ID of the new member and the cluster ID.

Example

./etcdctl member add newMember --peer-urls=https://127.0.0.1:12345

Member ced000fda4d05edf added to cluster 8c4281cc65c7b112

ETCD_NAME="newMember"
ETCD_INITIAL_CLUSTER="newMember=https://127.0.0.1:12345,default=http://10.0.0.30:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

MEMBER UPDATE <memberID> [options]

MEMBER UPDATE sets the peer URLs for an existing member in the etcd cluster.

RPC: MemberUpdate

Options

  • peer-urls – comma separated list of URLs to associate with the updated member.

Output

Prints the member ID of the updated member and the cluster ID.

Example

./etcdctl member update 2be1eb8f84b7f63e --peer-urls=https://127.0.0.1:11112
# Member 2be1eb8f84b7f63e updated in cluster ef37ad9dc622a7c4

MEMBER REMOVE <memberID>

MEMBER REMOVE removes a member of an etcd cluster from participating in cluster consensus.

RPC: MemberRemove

Output

Prints the member ID of the removed member and the cluster ID.

Example

./etcdctl member remove 2be1eb8f84b7f63e
# Member 2be1eb8f84b7f63e removed from cluster ef37ad9dc622a7c4

MEMBER LIST

MEMBER LIST prints the member details for all members associated with an etcd cluster.

RPC: MemberList

Output

Prints a humanized table of the member IDs, statuses, names, peer addresses, and client addresses.

Examples

./etcdctl member list
# 8211f1d0f64f3269, started, infra1, http://127.0.0.1:12380, http://127.0.0.1:2379
# 91bc3c398fb3c146, started, infra2, http://127.0.0.1:22380, http://127.0.0.1:22379
# fd422379fda50e48, started, infra3, http://127.0.0.1:32380, http://127.0.0.1:32379
./etcdctl -w json member list
# {"header":{"cluster_id":17237436991929493444,"member_id":9372538179322589801,"raft_term":2},"members":[{"ID":9372538179322589801,"name":"infra1","peerURLs":["http://127.0.0.1:12380"],"clientURLs":["http://127.0.0.1:2379"]},{"ID":10501334649042878790,"name":"infra2","peerURLs":["http://127.0.0.1:22380"],"clientURLs":["http://127.0.0.1:22379"]},{"ID":18249187646912138824,"name":"infra3","peerURLs":["http://127.0.0.1:32380"],"clientURLs":["http://127.0.0.1:32379"]}]}
./etcdctl -w table member list
+------------------+---------+--------+------------------------+------------------------+
|        ID        | STATUS  |  NAME  |       PEER ADDRS       |      CLIENT ADDRS      |
+------------------+---------+--------+------------------------+------------------------+
| 8211f1d0f64f3269 | started | infra1 | http://127.0.0.1:12380 | http://127.0.0.1:2379  |
| 91bc3c398fb3c146 | started | infra2 | http://127.0.0.1:22380 | http://127.0.0.1:22379 |
| fd422379fda50e48 | started | infra3 | http://127.0.0.1:32380 | http://127.0.0.1:32379 |
+------------------+---------+--------+------------------------+------------------------+

ENDPOINT <subcommand>

ENDPOINT provides commands for querying individual endpoints.

Options

  • cluster – fetch and use all endpoints from the etcd cluster member list

ENDPOINT HEALTH

ENDPOINT HEALTH checks the health of the list of endpoints with respect to cluster. An endpoint is unhealthy
when it cannot participate in consensus with the rest of the cluster.

Output

If an endpoint can participate in consensus, prints a message indicating the endpoint is healthy. If an endpoint fails to participate in consensus, prints a message indicating the endpoint is unhealthy.

Example

Check the default endpoint’s health:

./etcdctl endpoint health
# 127.0.0.1:2379 is healthy: successfully committed proposal: took = 2.095242ms

Check all endpoints for the cluster associated with the default endpoint:

./etcdctl endpoint --cluster health
# http://127.0.0.1:2379 is healthy: successfully committed proposal: took = 1.060091ms
# http://127.0.0.1:22379 is healthy: successfully committed proposal: took = 903.138µs
# http://127.0.0.1:32379 is healthy: successfully committed proposal: took = 1.113848ms

ENDPOINT STATUS

ENDPOINT STATUS queries the status of each endpoint in the given endpoint list.

Output

Simple format

Prints a humanized table of each endpoint URL, ID, version, database size, leadership status, raft term, and raft status.

JSON format

Prints a line of JSON encoding each endpoint URL, ID, version, database size, leadership status, raft term, and raft status.

Examples

Get the status for the default endpoint:

./etcdctl endpoint status
# 127.0.0.1:2379, 8211f1d0f64f3269, 3.0.0, 25 kB, false, 2, 63

Get the status for the default endpoint as JSON:

./etcdctl -w json endpoint status
# [{"Endpoint":"127.0.0.1:2379","Status":{"header":{"cluster_id":17237436991929493444,"member_id":9372538179322589801,"revision":2,"raft_term":2},"version":"3.0.0","dbSize":24576,"leader":18249187646912138824,"raftIndex":32623,"raftTerm":2}}]

Get the status for all endpoints in the cluster associated with the default endpoint:

./etcdctl -w table endpoint --cluster status
+------------------------+------------------+----------------+---------+-----------+-----------+------------+
|        ENDPOINT        |        ID        |    VERSION     | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+------------------------+------------------+----------------+---------+-----------+-----------+------------+
| http://127.0.0.1:2379  | 8211f1d0f64f3269 | 3.2.0-rc.1+git |   25 kB |     false |         2 |          8 |
| http://127.0.0.1:22379 | 91bc3c398fb3c146 | 3.2.0-rc.1+git |   25 kB |     false |         2 |          8 |
| http://127.0.0.1:32379 | fd422379fda50e48 | 3.2.0-rc.1+git |   25 kB |      true |         2 |          8 |
+------------------------+------------------+----------------+---------+-----------+-----------+------------+

ENDPOINT HASHKV

ENDPOINT HASHKV fetches the hash of the key-value store of an endpoint.

Output

Simple format

Prints a humanized table of each endpoint URL and KV history hash.

JSON format

Prints a line of JSON encoding each endpoint URL and KV history hash.

Examples

Get the hash for the default endpoint:

./etcdctl endpoint hashkv
# 127.0.0.1:2379, 1084519789

Get the status for the default endpoint as JSON:

./etcdctl -w json endpoint hashkv
# [{"Endpoint":"127.0.0.1:2379","Hash":{"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":1,"raft_term":3},"hash":1084519789,"compact_revision":-1}}]

Get the status for all endpoints in the cluster associated with the default endpoint:

./etcdctl -w table endpoint --cluster hashkv
+------------------------+------------+
|        ENDPOINT        |    HASH    |
+------------------------+------------+
| http://127.0.0.1:2379  | 1084519789 |
| http://127.0.0.1:22379 | 1084519789 |
| http://127.0.0.1:32379 | 1084519789 |
+------------------------+------------+

ALARM <subcommand>

Provides alarm related commands

ALARM DISARM

alarm disarm Disarms all alarms

RPC: Alarm

Output

alarm:<alarm type> if alarm is present and disarmed.

Examples

./etcdctl alarm disarm

If NOSPACE alarm is present:

./etcdctl alarm disarm
# alarm:NOSPACE

ALARM LIST

alarm list lists all alarms.

RPC: Alarm

Output

alarm:<alarm type> if alarm is present, empty string if no alarms present.

Examples

./etcdctl alarm list

If NOSPACE alarm is present:

./etcdctl alarm list
# alarm:NOSPACE

DEFRAG [options]

DEFRAG defragments the backend database file for a set of given endpoints while etcd is running, or directly defragments an etcd data directory while etcd is not running. When an etcd member reclaims storage space from deleted and compacted keys, the space is kept in a free list and the database file remains the same size. By defragmenting the database, the etcd member releases this free space back to the file system.

Note that defragmentation to a live member blocks the system from reading and writing data while rebuilding its states.

Note that defragmentation request does not get replicated over cluster. That is, the request is only applied to the local node. Specify all members in --endpoints flag or --cluster flag to automatically find all cluster members.

Options

  • data-dir – Optional. If present, defragments a data directory not in use by etcd.

Output

For each endpoints, prints a message indicating whether the endpoint was successfully defragmented.

Example

./etcdctl --endpoints=localhost:2379,badendpoint:2379 defrag
# Finished defragmenting etcd member[localhost:2379]
# Failed to defragment etcd member[badendpoint:2379] (grpc: timed out trying to connect)

Run defragment operations for all endpoints in the cluster associated with the default endpoint:

./etcdctl defrag --cluster
Finished defragmenting etcd member[http://127.0.0.1:2379]
Finished defragmenting etcd member[http://127.0.0.1:22379]
Finished defragmenting etcd member[http://127.0.0.1:32379]

To defragment a data directory directly, use the --data-dir flag:

# Defragment while etcd is not running
./etcdctl defrag --data-dir default.etcd
# success (exit status 0)
# Error: cannot open database at default.etcd/member/snap/db

Remarks

DEFRAG returns a zero exit code only if it succeeded defragmenting all given endpoints.

SNAPSHOT <subcommand>

SNAPSHOT provides commands to restore a snapshot of a running etcd server into a fresh cluster.

SNAPSHOT SAVE <filename>

SNAPSHOT SAVE writes a point-in-time snapshot of the etcd backend database to a file.

Output

The backend snapshot is written to the given file path.

Example

Save a snapshot to “snapshot.db”:

./etcdctl snapshot save snapshot.db

SNAPSHOT RESTORE [options] <filename>

SNAPSHOT RESTORE creates an etcd data directory for an etcd cluster member from a backend database snapshot and a new cluster configuration. Restoring the snapshot into each member for a new cluster configuration will initialize a new etcd cluster preloaded by the snapshot data.

Options

The snapshot restore options closely resemble to those used in the etcd command for defining a cluster.

  • data-dir – Path to the data directory. Uses <name>.etcd if none given.

  • wal-dir – Path to the WAL directory. Uses data directory if none given.

  • initial-cluster – The initial cluster configuration for the restored etcd cluster.

  • initial-cluster-token – Initial cluster token for the restored etcd cluster.

  • initial-advertise-peer-urls – List of peer URLs for the member being restored.

  • name – Human-readable name for the etcd cluster member being restored.

  • skip-hash-check – Ignore snapshot integrity hash value (required if copied from data directory)

Output

A new etcd data directory initialized with the snapshot.

Example

Save a snapshot, restore into a new 3 node cluster, and start the cluster:

./etcdctl snapshot save snapshot.db

# restore members
bin/etcdctl snapshot restore snapshot.db --initial-cluster-token etcd-cluster-1 --initial-advertise-peer-urls http://127.0.0.1:12380  --name sshot1 --initial-cluster 'sshot1=http://127.0.0.1:12380,sshot2=http://127.0.0.1:22380,sshot3=http://127.0.0.1:32380'
bin/etcdctl snapshot restore snapshot.db --initial-cluster-token etcd-cluster-1 --initial-advertise-peer-urls http://127.0.0.1:22380  --name sshot2 --initial-cluster 'sshot1=http://127.0.0.1:12380,sshot2=http://127.0.0.1:22380,sshot3=http://127.0.0.1:32380'
bin/etcdctl snapshot restore snapshot.db --initial-cluster-token etcd-cluster-1 --initial-advertise-peer-urls http://127.0.0.1:32380  --name sshot3 --initial-cluster 'sshot1=http://127.0.0.1:12380,sshot2=http://127.0.0.1:22380,sshot3=http://127.0.0.1:32380'

# launch members
bin/etcd --name sshot1 --listen-client-urls http://127.0.0.1:2379 --advertise-client-urls http://127.0.0.1:2379 --listen-peer-urls http://127.0.0.1:12380 &
bin/etcd --name sshot2 --listen-client-urls http://127.0.0.1:22379 --advertise-client-urls http://127.0.0.1:22379 --listen-peer-urls http://127.0.0.1:22380 &
bin/etcd --name sshot3 --listen-client-urls http://127.0.0.1:32379 --advertise-client-urls http://127.0.0.1:32379 --listen-peer-urls http://127.0.0.1:32380 &

SNAPSHOT STATUS <filename>

SNAPSHOT STATUS lists information about a given backend database snapshot file.

Output

Simple format

Prints a humanized table of the database hash, revision, total keys, and size.

JSON format

Prints a line of JSON encoding the database hash, revision, total keys, and size.

Examples

./etcdctl snapshot status file.db
# cf1550fb, 3, 3, 25 kB
./etcdctl -write-out=json snapshot status file.db
# {"hash":3474280699,"revision":3,"totalKey":3,"totalSize":24576}
./etcdctl -write-out=table snapshot status file.db
+----------+----------+------------+------------+
|   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| cf1550fb |        3 |          3 | 25 kB      |
+----------+----------+------------+------------+

MOVE-LEADER <hexadecimal-transferee-id>

MOVE-LEADER transfers leadership from the leader to another member in the cluster.

Example

# to choose transferee
transferee_id=$(./etcdctl \
  --endpoints localhost:2379,localhost:22379,localhost:32379 \
  endpoint status | grep -m 1 "false" | awk -F', ' '{print $2}')
echo ${transferee_id}
# c89feb932daef420

# endpoints should include leader node
./etcdctl --endpoints ${transferee_ep} move-leader ${transferee_id}
# Error:  no leader endpoint given at [localhost:22379 localhost:32379]

# request to leader with target node ID
./etcdctl --endpoints ${leader_ep} move-leader ${transferee_id}
# Leadership transferred from 45ddc0e800e20b93 to c89feb932daef420

Concurrency commands

LOCK [options] <lockname> [command arg1 arg2 …]

LOCK acquires a distributed mutex with a given name. Once the lock is acquired, it will be held until etcdctl is terminated.

Options

  • ttl - time out in seconds of lock session.

Output

Once the lock is acquired but no command is given, the result for the GET on the unique lock holder key is displayed.

If a command is given, it will be executed with environment variables ETCD_LOCK_KEY and ETCD_LOCK_REV set to the lock’s holder key and revision.

Example

Acquire lock with standard output display:

./etcdctl lock mylock
# mylock/1234534535445

Acquire lock and execute echo lock acquired:

./etcdctl lock mylock echo lock acquired
# lock acquired

Acquire lock and execute etcdctl put command

./etcdctl lock mylock ./etcdctl put foo bar
# OK

Remarks

LOCK returns a zero exit code only if it is terminated by a signal and releases the lock.

If LOCK is abnormally terminated or fails to contact the cluster to release the lock, the lock will remain held until the lease expires. Progress may be delayed by up to the default lease length of 60 seconds.

ELECT [options] <election-name> [proposal]

ELECT participates on a named election. A node announces its candidacy in the election by providing
a proposal value. If a node wishes to observe the election, ELECT listens for new leaders values.
Whenever a leader is elected, its proposal is given as output.

Options

  • listen – observe the election.

Output

  • If a candidate, ELECT displays the GET on the leader key once the node is elected election.

  • If observing, ELECT streams the result for a GET on the leader key for the current election and all future elections.

Example

./etcdctl elect myelection foo
# myelection/1456952310051373265
# foo

Remarks

ELECT returns a zero exit code only if it is terminated by a signal and can revoke its candidacy or leadership, if any.

If a candidate is abnormally terminated, election rogress may be delayed by up to the default lease length of 60 seconds.

Authentication commands

AUTH <enable or disable>

auth enable activates authentication on an etcd cluster and auth disable deactivates. When authentication is enabled, etcd checks all requests for appropriate authorization.

RPC: AuthEnable/AuthDisable

Output

Authentication Enabled.

Examples

./etcdctl user add root
# Password of root:#type password for root
# Type password of root again for confirmation:#re-type password for root
# User root created
./etcdctl user grant-role root root
# Role root is granted to user root
./etcdctl user get root
# User: root
# Roles: root
./etcdctl role add root
# Role root created
./etcdctl role get root
# Role root
# KV Read:
# KV Write:
./etcdctl auth enable
# Authentication Enabled

ROLE <subcommand>

ROLE is used to specify different roles which can be assigned to etcd user(s).

ROLE ADD <role name>

role add creates a role.

RPC: RoleAdd

Output

Role <role name> created.

Examples

./etcdctl --user=root:123 role add myrole
# Role myrole created

ROLE GET <role name>

role get lists detailed role information.

RPC: RoleGet

Output

Detailed role information.

Examples

./etcdctl --user=root:123 role get myrole
# Role myrole
# KV Read:
# foo
# KV Write:
# foo

ROLE DELETE <role name>

role delete deletes a role.

RPC: RoleDelete

Output

Role <role name> deleted.

Examples

./etcdctl --user=root:123 role delete myrole
# Role myrole deleted

ROLE LIST <role name>

role list lists all roles in etcd.

RPC: RoleList

Output

A role per line.

Examples

./etcdctl --user=root:123 role list
# roleA
# roleB
# myrole

ROLE GRANT-PERMISSION [options] <role name> <permission type> <key> [endkey]

role grant-permission grants a key to a role.

RPC: RoleGrantPermission

Options

  • from-key – grant a permission of keys that are greater than or equal to the given key using byte compare

  • prefix – grant a prefix permission

Output

Role <role name> updated.

Examples

Grant read and write permission on the key foo to role myrole:

./etcdctl --user=root:123 role grant-permission myrole readwrite foo
# Role myrole updated

Grant read permission on the wildcard key pattern foo/* to role myrole:

./etcdctl --user=root:123 role grant-permission --prefix myrole readwrite foo/
# Role myrole updated

ROLE REVOKE-PERMISSION <role name> <permission type> <key> [endkey]

role revoke-permission revokes a key from a role.

RPC: RoleRevokePermission

Options

  • from-key – revoke a permission of keys that are greater than or equal to the given key using byte compare

  • prefix – revoke a prefix permission

Output

Permission of key <key> is revoked from role <role name> for single key. Permission of range [<key>, <endkey>) is revoked from role <role name> for a key range. Exit code is zero.

Examples

./etcdctl --user=root:123 role revoke-permission myrole foo
# Permission of key foo is revoked from role myrole

USER <subcommand>

USER provides commands for managing users of etcd.

USER ADD <user name or user:password> [options]

user add creates a user.

RPC: UserAdd

Options

  • interactive – Read password from stdin instead of interactive terminal

Output

User <user name> created.

Examples

./etcdctl --user=root:123 user add myuser
# Password of myuser: #type password for my user
# Type password of myuser again for confirmation:#re-type password for my user
# User myuser created

USER GET <user name> [options]

user get lists detailed user information.

RPC: UserGet

Options

  • detail – Show permissions of roles granted to the user

Output

Detailed user information.

Examples

./etcdctl --user=root:123 user get myuser
# User: myuser
# Roles:

USER DELETE <user name>

user delete deletes a user.

RPC: UserDelete

Output

User <user name> deleted.

Examples

./etcdctl --user=root:123 user delete myuser
# User myuser deleted

USER LIST

user list lists detailed user information.

RPC: UserList

Output

  • List of users, one per line.

Examples

./etcdctl --user=root:123 user list
# user1
# user2
# myuser

USER PASSWD <user name> [options]

user passwd changes a user’s password.

RPC: UserChangePassword

Options

  • interactive – if true, read password in interactive terminal

Output

Password updated.

Examples

./etcdctl --user=root:123 user passwd myuser
# Password of myuser: #type new password for my user
# Type password of myuser again for confirmation: #re-type the new password for my user
# Password updated

USER GRANT-ROLE <user name> <role name>

user grant-role grants a role to a user

RPC: UserGrantRole

Output

Role <role name> is granted to user <user name>.

Examples

./etcdctl --user=root:123 user grant-role userA roleA
# Role roleA is granted to user userA

USER REVOKE-ROLE <user name> <role name>

user revoke-role revokes a role from a user

RPC: UserRevokeRole

Output

Role <role name> is revoked from user <user name>.

Examples

./etcdctl --user=root:123 user revoke-role userA roleA
# Role roleA is revoked from user userA

Utility commands

MAKE-MIRROR [options] <destination>

make-mirror mirrors a key prefix in an etcd cluster to a destination etcd cluster.

Options

  • dest-cacert – TLS certificate authority file for destination cluster

  • dest-cert – TLS certificate file for destination cluster

  • dest-key – TLS key file for destination cluster

  • prefix – The key-value prefix to mirror

  • dest-prefix – The destination prefix to mirror a prefix to a different prefix in the destination cluster

  • no-dest-prefix – Mirror key-values to the root of the destination cluster

  • dest-insecure-transport – Disable transport security for client connections

Output

The approximate total number of keys transferred to the destination cluster, updated every 30 seconds.

Examples

./etcdctl make-mirror mirror.example.com:2379
# 10
# 18

MIGRATE [options]

Migrates keys in a v2 store to a v3 mvcc store. Users should run migration command for all members in the cluster.

Options

  • data-dir – Path to the data directory

  • wal-dir – Path to the WAL directory

  • transformer – Path to the user-provided transformer program (default if not provided)

Output

No output on success.

Default transformer

If user does not provide a transformer program, migrate command will use the default transformer. The default transformer transforms storev2 formatted keys into mvcc formatted keys according to the following Go program:

func transform(n *storev2.Node) *mvccpb.KeyValue {
	if n.Dir {
		return nil
	}
	kv := &mvccpb.KeyValue{
		Key:            []byte(n.Key),
		Value:          []byte(n.Value),
		CreateRevision: int64(n.CreatedIndex),
		ModRevision:    int64(n.ModifiedIndex),
		Version:        1,
	}
	return kv
}

User-provided transformer

Users can provide a customized 1:n transformer function that transforms a key from the v2 store to any number of keys in the mvcc store. The migration program writes JSON formatted v2 store keys to the transformer program’s stdin, reads protobuf formatted mvcc keys back from the transformer program’s stdout, and finishes migration by saving the transformed keys into the mvcc store.

The provided transformer should read until EOF and flush the stdout before exiting to ensure data integrity.

Example

./etcdctl migrate --data-dir=/var/etcd --transformer=k8s-transformer
# finished transforming keys

VERSION

Prints the version of etcdctl.

Output

Prints etcd version and API version.

Examples

./etcdctl version
# etcdctl version: 3.1.0-alpha.0+git
# API version: 3.1

CHECK <subcommand>

CHECK provides commands for checking properties of the etcd cluster.

CHECK PERF [options]

CHECK PERF checks the performance of the etcd cluster for 60 seconds. Running the check perf often can create a large keyspace history which can be auto compacted and defragmented using the --auto-compact and --auto-defrag options as described below.

RPC: CheckPerf

Options

  • load – the performance check’s workload model. Accepted workloads: s(small), m(medium), l(large), xl(xLarge)

  • prefix – the prefix for writing the performance check’s keys.

  • auto-compact – if true, compact storage with last revision after test is finished.

  • auto-defrag – if true, defragment storage after test is finished.

Output

Prints the result of performance check on different criteria like throughput. Also prints an overall status of the check as pass or fail.

Examples

Shows examples of both, pass and fail, status. The failure is due to the fact that a large workload was tried on a single node etcd cluster running on a laptop environment created for development and testing purpose.

./etcdctl check perf --load="s"
# 60 / 60 Booooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo! 100.00%1m0s
# PASS: Throughput is 150 writes/s
# PASS: Slowest request took 0.087509s
# PASS: Stddev is 0.011084s
# PASS
./etcdctl check perf --load="l"
# 60 / 60 Booooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo! 100.00%1m0s
# FAIL: Throughput too low: 6808 writes/s
# PASS: Slowest request took 0.228191s
# PASS: Stddev is 0.033547s
# FAIL

CHECK DATASCALE [options]

CHECK DATASCALE checks the memory usage of holding data for different workloads on a given server endpoint. Running the check datascale often can create a large keyspace history which can be auto compacted and defragmented using the --auto-compact and --auto-defrag options as described below.

RPC: CheckDatascale

Options

  • load – the datascale check’s workload model. Accepted workloads: s(small), m(medium), l(large), xl(xLarge)

  • prefix – the prefix for writing the datascale check’s keys.

  • auto-compact – if true, compact storage with last revision after test is finished.

  • auto-defrag – if true, defragment storage after test is finished.

Output

Prints the system memory usage for a given workload. Also prints status of compact and defragment if related options are passed.

Examples

./etcdctl check datascale --load="s" --auto-compact=true --auto-defrag=true
# Start data scale check for work load [10000 key-value pairs, 1024 bytes per key-value, 50 concurrent clients].
# Compacting with revision 18346204
# Compacted with revision 18346204
# Defragmenting "127.0.0.1:2379"
# Defragmented "127.0.0.1:2379"
# PASS: Approximate system memory used : 64.30 MB.

Exit codes

For all commands, a successful execution return a zero exit code. All failures will return non-zero exit codes.

Output formats

All commands accept an output format by setting -w or --write-out. All commands default to the “simple” output format, which is meant to be human-readable. The simple format is listed in each command’s Output description since it is customized for each command. If a command has a corresponding RPC, it will respect all output formats.

If a command fails, returning a non-zero exit code, an error string will be written to standard error regardless of output format.

Simple

A format meant to be easy to parse and human-readable. Specific to each command.

JSON

The JSON encoding of the command’s RPC response. Since etcd’s RPCs use byte strings, the JSON output will encode keys and values in base64.

Some commands without an RPC also support JSON; see the command’s Output description.

Protobuf

The protobuf encoding of the command’s RPC response. If an RPC is streaming, the stream messages will be concetenated. If an RPC is not given for a command, the protobuf output is not defined.

Fields

An output format similar to JSON but meant to parse with coreutils. For an integer field named Field, it writes a line in the format "Field" : %d where %d is go’s integer formatting. For byte array fields, it writes "Field" : %q where %q is go’s quoted string formatting (e.g., []byte{'a', '\n'} is written as "a\n").

Compatibility Support

etcdctl is still in its early stage. We try out best to ensure fully compatible releases, however we might break compatibility to fix bugs or improve commands. If we intend to release a version of etcdctl with backward incompatibilities, we will provide notice prior to release and have instructions on how to upgrade.

Input Compatibility

Input includes the command name, its flags, and its arguments. We ensure backward compatibility of the input of normal commands in non-interactive mode.

Output Compatibility

Output includes output from etcdctl and its exit code. etcdctl provides simple output format by default.
We ensure compatibility for the simple output format of normal commands in non-interactive mode. Currently, we do not ensure
backward compatibility for JSON format and the format in non-interactive mode. Currently, we do not ensure backward compatibility of utility commands.

TODO: compatibility with etcd server

service

参考:https://www.cnblogs.com/fuyuteng/p/11598768.html
kubernetes service 原理解析 - 知乎 (zhihu.com)

为什么需要 service

在 kubernetes 中,当创建带有多个副本的 deployment 时,kubernetes 会创建出多个 pod,此时即一个服务后端有多个容器,那么在 kubernetes 中负载均衡怎么做,容器漂移后 ip 也会发生变化,如何做服务发现以及会话保持?这就是 service 的作用,service 是一组具有相同 label pod 集合的抽象,集群内外的各个服务可以通过 service 进行互相通信,当创建一个 service 对象时也会对应创建一个 endpoint 对象,endpoint 是用来做容器发现的,service 只是将多个 pod 进行关联,实际的路由转发都是由 kubernetes 中的 kube-proxy 组件来实现,因此,service 必须结合 kube-proxy 使用,kube-proxy 组件可以运行在 kubernetes 集群中的每一个节点上也可以只运行在单独的几个节点上,其会根据 service 和 endpoints 的变动来改变节点上 iptables 或者 ipvs 中保存的路由规则

service 的工作原理

service资源基于标签选择器将一组pod定义成一个逻辑组合,并通过自己的ip地址和端口调度代理请求至组内pod的对象之上,它向客户隐藏了真实的、处理用户请求的pod资源,使得客户端的请求看上去像是由service直接处理并进行响应一样

Service 对象的 IP 地址也称为 Cluster IP,是一种 VIP(虚拟IP),k8s集群内部的VIP

service网段不能跟机房网络、docker网段、容器网段冲突,否则可能会导致网络不通

Service以负载均衡的方式进行流量调度,Service和Pod之间松耦合,创建Service和Pod的任务可由不同的用户分别完成

Service 通过API Server实时监控(watch)标签选择器匹配到的后端Pod,不过Service并不直接链接至Pod,它们中间还有一个中间层 --Endpoints资源对象,默认情况下,创建Service对象时,其关联的Endpoints对象会自动创建

endpoints controller 是负责生成和维护所有 endpoints 对象的控制器,监听 service 和对应 pod 的变化,更新对应 service 的 endpoints 对象。当用户创建 service 后 endpoints controller 会监听 pod 的状态,当 pod 处于 running 且准备就绪时,endpoints controller 会将 pod ip 记录到 endpoints 对象中,因此,service 的容器发现是通过 endpoints 来实现的。而 kube-proxy 会监听 service 和 endpoints 的更新并调用其代理模块在主机上刷新路由转发规则

Endpoints 是一个由IP地址和端口组成的列表,这些IP和端口来自于其 Service 关联的 Pod

每个节点上的 kube-proxy 组件 watch 各 Service 及其关联的 Endpoints,如果有变动,就会实时更新当前节点上相应的 iptables 或 ipvs 规则,确保 Cluster IP 的流量能调度到 Endpoints

简单来讲,一个 Service 对象就是一个 Node 上的这些 iptables 和 ipvs 规则

service 的负载均衡

pod之间通信,一般不会pod直接访问pod,而是pod先访问service,然后service再到pod

关于将 Cluster IP 的流量能调度到 Endpoints,有三种方式:userspace代理、iptables代理、ipvs代理

  1. userspace代理

    1.1版本之前的默认代理模型,效率低

  2. iptables代理

    通过iptables进行目标地址转换和流量调度,缺点是不会在后端Pod资源无响应时自动进行重定向

  3. ipvs代理

    和iptables代理模型的区别仅在于,流量的调度功能由ipvs实现,其他功能仍由iptables完成

service 的类型

service 支持的类型也就是 kubernetes 中服务暴露的方式,默认有四种:ClusterIP、NodePort、LoadBalancer、ExternelName

ClusterIP

kubernetes 集群默认的服务暴露方式,它只能用于集群内部通信,可以被各 pod 访问,其访问方式为:

pod ---> ClusterIP:ServicePort --> (iptables)DNAT --> PodIP:containePort

# 集群内pod直接访问service的ip即可

NodePort

如果想要在集群外访问集群内部的服务,可以使用 NodePort 类型的 service,在集群内部署了 kube-proxy 的节点打开一个指定的端口,将访问node此端口的流量直接发送到这个端口,然后会被转发到 service 后端真实的服务进行访问。Nodeport 构建在 ClusterIP 上,其访问链路如下所示:

client ---> NodeIP:NodePort ---> ClusterIP:ServicePort ---> (iptables)DNAT ---> PodIP:containePort

# 外部流量请求先到node,再到service
# 只要安装了kube-proxy的node,都可以处理外部流量,有实力的话可以单独拿出几个node,打上污点,然后负载均衡只会将外部流量转发到这几个node

LoadBalancer

主要在公有云如阿里云、AWS 上使用,LoadBalancer 构建在 nodePort 基础之上,通过公有云服务商提供的负载均衡器将 k8s 集群中的服务暴露到外网,云厂商的 LoadBalancer 会给用户分配一个 IP,之后通过该 IP 的流量会转发到你的 service 上

LoadBalancer service 类型的结构如下图所示:

ExternelName

通过 CNAME 将 service 与 externalName 的值(比如:http://foo.bar.example.com)映射起来,这种方式用的比较少。

service 的服务发现

Pod 与 Service 的 DNS:https://kubernetes.io/zh/docs/concepts/services-networking/dns-pod-service/

虽然 service 的 endpoints 解决了容器发现问题,但不提前知道 service 的 Cluster IP,怎么发现 service 服务呢?service 当前支持两种类型的服务发现机制,一种是通过环境变量,另一种是通过 DNS。在这两种方案中,建议使用后者:

在集群中部署 CoreDNS 服务, 来达到集群内部的 pod 通过DNS 的方式进行集群内部各个服务之间的通讯

当前 kubernetes 集群默认使用 CoreDNS 作为默认的 DNS 服务,主要原因是 CoreDNS 是基于 Plugin 的方式进行扩展的,简单,灵活,并且不完全被Kubernetes所捆绑

service 的使用

ClusterIP 方式

apiVersion: v1
kind: Service
metadata:
  name: my-nginx #service的名称,此名称会被DNS解析
spec:
  clusterIP: 10.105.146.177
  ports:
    - port: 80 # Service的端口号
      protocol: TCP
      targetPort: 8080 # 后端目标进程的端口号或名称,名称需由Pod规范定义
  selector:
    app: my-nginx
  sessionAffinity: None
  type: ClusterIP

NodePort 方式

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  ports:
    - nodePort: 30090 # 节点端口,kube-proxy监听本机此端口,将访问此端口的流量转发到service
      port: 80 # service端口
      protocol: TCP
      targetPort: 8080 # 目标pod端口
  selector:
    app: my-nginx
  sessionAffinity: None
  type: NodePort

Headless service(就是没有 Cluster IP 的 service )

当不需要负载均衡以及单独的 ClusterIP 时,可以通过指定 spec.clusterIP 的值为 None 来创建 Headless service,它会给一个集群内部的每个成员提供一个唯一的 DNS 域名来作为每个成员的网络标识,集群内部成员之间使用域名通信。

deployment中对应的服务是 service,而statefulset中与之对应的服务就是 headless service

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  clusterIP: None
  ports:
  - nodePort: 30090
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: my-nginx

Ingress

https://kubernetes.io/zh/docs/concepts/services-networking/ingress/
https://kubernetes.io/zh/docs/concepts/services-networking/ingress-controllers/

上面介绍的 ClusterIP 、NodePort 、LoadBalancer 都是基于ip和port做负载均衡,属于四层负载均衡

Ingress 可以作用于多个 service,被称为 service 的 service,作为集群内部服务的入口,Ingress其实就是七层的反向代理,可以根据不同的 url,将请求转发到不同的 service 上

Ingress 的结构如下图所示:

kube-proxy监听node的指定端口,将此端口的流量转发到ingress,然后ingress再转发到不同的service

反正都是实现七层代理,也可以使用deployment创建一组pod,提供nginx服务,实现代替ingress的效果:

kube-proxy监听node的指定端口,将此端口的流量转发到nginx service,然后nginx在转发到不同的service,可以配置service ip,也可以使用服务发现

建议:域名多、流量大,使用nginx;反之使用ingress

其他服务类型

完全不使用k8s内置的service,而是通过注册中心自动发现pod地址,如果开发有实力,推荐使用这种方式

网上的教程基本都给dumpcap设置suid,修改dumpcap的属组为wireshark,将whoami添加到wireshark附属组。其实后面的都是多余操作,只需给dumpcap设置suid即可。

sudo chmod u+s /usr/bin/dumpcap

什么是k8s?

kubernetes:容器的管理和编排系统

k8s由多个组件组成,部署在一群服务器之上,将所有服务器整合成一个资源池,然后向客户端提供各种接口, 客户端只需要调用相应接口就可以管理容器,至于底层容器具体跑在哪台服务器,不可见也不需要关心

针对各种特定的业务,尤其是比较依赖工程师经验的业务(例如数据库集群宕机后的数据恢复、各种集群的扩缩容),可以将一系列复杂操作代码化,这个代码在k8s中就叫operator,只要用好各种operator,就可以方便高效的解决各种问题,这样,运维工程师的主要工作就变成了维护k8s,确保k8s自身能够良好运行

k8s内置了各种operator,但是这些operator更重视通用性,很难完全匹配实际工作需要,所以就需要对k8s进行二次开发,编写各种operator,这也是SRE工程师的必备技能之一

operator能单独管理应用集群,实现复杂操作;controllor:控制器,只能实现简单的容器操作

开发人员为k8s开发应用程序的时候,通常不会完整的部署一个分布式k8s集群,而是使用一个叫做MiniKube,它可以在单机上模拟出一个完整意义上的k8s集群

节点

资源池中的各个服务器叫做节点,节点有两种:

  • Worker Node:运行Pod,一般称 Node
  • Master Node:运行控制平面组件,不运行pod,一般称为 Master

组件

k8s组件分为三种: 控制平面组件Node组件Addons

https://kubernetes.io/docs/concepts/overview/components/

控制平面组件

控制平面(control plane)管理Node和Node上的Pods

控制平面组件可以分别运行在任意节点上,但是为了简单,通常会将所有控制平面组件运行在同一个节点上,还可以以副本的形式运行在多个节点上,然后禁止在这种节点上运行pod,这种节点就叫做Master Node(后文简称Master),当集群中只有一个Master时,就是单控制平面,有多个Master时,就是多控制平面,生产中肯定是多控制平面,但是学习中一般只使用单控制平面

kube-apiserver

https服务器,监听在6443端口,它将k8s集群内的一切都抽象成资源,提供RESTful风格的API,对资源(对象)进行增删改查等管理操作

apiserver是整个k8s系统的总线,所有组件之间,都是通过它进行协同,它是唯一可以存储k8s集群的状态信息的组件(储存在Etcd)

生产中,apiserver需要做冗余,因为无状态,所以最少部署两个apiserver,因为是https服务器,所以需要做四层负载,使用Nginx、HAProxy、LVS均可,然后搭配keepalived给负载均衡做高可用

关于健康监测,分为AH(主动监测)和PH(被动监测或者叫异常值探测)

kube-controller-manager

控制器管理器,负责管理控制器,真正意义上让k8s所有功能得以实现的控制中心,controller-manager中有很多controller(deployment等数十种),这些controller才是真正意义上的k8s控制中心,负责集群内的Node、Pod副本、服务端点(Endpoint)、命名空间(Namespace)、服务账号(ServiceAccount)、资源定额(ResourceQuota)的管理

当某个Node意外宕机时,Controller Manager会及时发现并执行自动化修复流程,确保集群始终处于预期(yaml配置文件指定)的工作状态

k8s可以把运维人员日常重复性的工作代码化,就是将多controller打包起来,单一运行

默认监听本机的10252端口

controller loop:控制循环

kube-scheduler

调度器,调度pod,它的核心作用就是运行应用,scheduler时刻关注着每个节点的资源可用量,以及运行pod所需的资源量,让二者达到最佳匹配,让pod以最好的状态运行

在整个系统中起”承上启下”作用,承上:负责接收Controller Manager创建的新的Pod,为其选择一个合适的Node;启下:Node上的kubelet接管Pod的生命周期

通过调度算法为待调度Pod列表的每个Pod从可用Node列表中选择一个最适合的Node,并将信息写入etcd中node节点上的kubelet通过API Server监听到kubernetes Scheduler产生的Pod绑定信息,然后获取对应的Pod清
单,下载Image,并启动容器

优选策略:
1.LeastRequestedPriority
优先从备选节点列表中选择资源消耗最小的节点(CPU+内存)
2.CalculateNodeLabelPriority
优先选择含有指定Label的节点。
3.BalancedResourceAllocation
优先从备选节点列表中选择各项资源使用率最均衡的节点

etcd

https://etcd.io/
https://github.com/etcd-io/etcd

第三方、非k8s内置,它的目标是构建一个高可用的分布式键值(key-value)数据库,为避免脑裂,通常部署3个或5个节点

完全复制 	# 集群中的每个节点都可以使用完整的存档
高可用性 	# Etcd可用于避免硬件的单点故障或网络问题
一致性 		# 每次读取都会返回跨多主机的最新写入
简单   		# 包括一个定义良好、面向用户的API(gRPC)
安全   		# 实现了带有可选的客户端证书身份验证的自动化TLS
快速   		# 每秒10000次写入的基准速度
可靠   		# 使用Raft算法实现了存储的合理分布Etcd的工作原理

etcd有多个不同的API访问版本,v1版本已经废弃,etcd v2 和 v3 本质上是共享同一套 raft 协议代码的两个独立的应用,接口不一样,存储不一样,数据互相隔离。也就是说如果从 Etcd v2 升级到 Etcd v3,原来v2 的数据还是只能用 v2 的接口访问,v3 的接口创建的数据也只能访问通过 v3 的接口访问

以下以内容以 v3 为准

etcdctl 是 etcd 的命令行客户端工具

etcdctl [options] command [command options] [arguments...]
管理成员
etcdctl member list
etcdctl member add
etcdctl member promote
etcdctl member remove
etcdctl member update

验证成员状态:

$etcdctl endpoint health \
    --endpoints=https://10.0.1.31:2379 \
    --cacert=/etc/kubernetes/ssl/ca.pem \
    --cert=/etc/etcd/ssl/etcd.pem \
    --key=/etc/etcd/ssl/etcd-key.pem

# 多个成员写个遍历即可
增删改查
  • 增 put

    etcdctl put [options] <key> <value> (<value> can also be given from stdin) [flags]
    
    $etcdctl put /testkey "test data"
    OK
    
    $etcdctl get --print-value-only /testkey
    test data
  • 删 del

    etcdctl del [options] <key> [range_end] [flags]
    
    $etcdctl del /testkey
    1
  • 改 put

    直接覆盖即可

    $etcdctl put /testkey "test data2"
    OK
  • 查 get

    etcdctl get [options] <key> [range_end] [flags]
    
    $etcdctl get --print-value-only /testkey
    test data2
    
    $etcdctl get --prefix --keys-only /		# 获取所有key
    $etcdctl get --prefix --keys-only /calico
    $etcdctl get --prefix --keys-only /registry
    $etcdctl get --prefix --keys-only /registry/services
    $etcdctl get /calico/ipam/v2/handle/ipip-tunnel-addr-k8s-master.ljk.local
watch机制

etcd v3 的watch机制支持watch某个固定的key,也支持watch一个范围,发生变化就主动触发通知客户端

相比Etcd v2, Etcd v3的一些主要变化:

1. 接口通过grpc提供rpc接口,放弃了v2的http接口,优势是长连接效率提升明显,缺点是使用不如以前方便,尤其对不方便维护长连接的场景。
2. 废弃了原来的目录结构,变成了纯粹的kv,用户可以通过前缀匹配模式模拟目录
3. 内存中不再保存value,同样的内存可以支持存储更多的key
4. watch机制更稳定,基本上可以通过watch机制实现数据的完全同步
5. 提供了批量操作以及事务机制,用户可以通过批量事务请求来实现Etcd v2的CAS机制(批量事务支持if条件判断)

watch测试:

# 在 etcd node1 上watch一个key,没有此 key 也可以执行 watch,后期可以再创建
$etcdctl watch /testkey

# 在 etcd node2 修改数据,验证 etcd node1 是否能够发现数据变化
$etcdctl put /testkey "test for new"
OK
数据备份与恢复机制

v2 版本数据备份与恢复:

# 备份
etcdctl backup --data-dir /var/lib/etcd/ --backup-dir /opt/etcd_backup

# 恢复
etcd --data-dir=/var/lib/etcd/default.etcd --force-new-cluster &

v3 版本数据备份与恢复:

etcdctl snapshot <subcommand> [flags]

subcommand:save、restore、status
$etcdctl snapshot save snapshot.db		# 备份
...
Snapshot saved at snapshot.db
$file snapshot.db 
snapshot.db: data

# 恢复,将数据恢复到一个新的不存在的目录中
etcdctl snapshot restore snapshot.db --data-dir=/opt/etcd-testdir

cloud-controller-manager

略…

Node组件

Node组件运行在所有的节点上,包括Master

kubelet

与api server建立联系,监视api server中与自己node相关的pod的变动信息,执行指令操作

在 kubernetes 集群中,每个 Node 节点都会启动 kubelet 进程,处理 Master 节点下发到本节点的任务,管理 Pod 和其中的容器。kubelet 会在 API Server 上注册节点信息,定期向 Master 汇报节点资源使用情况,并通过cAdvisor(顾问)监控容器和节点资源,可以把 kubelet 理解成 Server/Agent 架构中的 agent,kubelet 是 Node上的 pod 管家

kube-porxy

https://kubernetes.io/zh/docs/concepts/services-networking/service/
https://kubernetes.io/zh/docs/reference/command-line-tools-reference/kube-proxy/

守护进程,管理当前节点的 iptables 或 ipvs 规则,而且管理的只是和 service 相关的规则

监控 service,把集群上的每一个 service 的定义转换为本地的 ipvs 或 iptables 规则

kube-proxy 是运行在集群中的每个节点上的网络代理,实现了 Kubernetes 服务概念的一部分
kube-proxy 维护节点上的网络规则。这些网络规则允许从集群内部或外部的网络会话到 Pods 进行网络通信
kube-proxy 使用操作系统包过滤层(如果有的话)并且它是可用的。否则,kube-proxy 将自己转发流量

Container runtime

通常是docker,其他类型的容器也支持

Addons

附加组件扩展了Kubernetes的功能

插件使用Kubernetes资源(DaemonSet, Deployment,等等)来实现集群特性。因为它们提供了集群级的特性,所以插件的命名空间资源属于kube-system命名空间

DNS

CoreDNS,k8s中,DNS是至关重要的,所有的访问都不会基于ip,而是基于name,name再通过DNS解析成ip

Web UI

集群监控系统

prometheus

集群日志系统

EFK、LG

Ingress Controller

入栈流量控制器,是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。

附件千千万,按需部署

CNI

CNI:Container Network Interface,容器网络接口

kubernetes 的网络插件遵从 CNI 规范的 v0.4.0 版本

网络插件有很多,最常用的是 flannel 和 Project Calico,生产环境用后者的最多

跨主机 pod 之间通信,有两种虚拟网络,有两种:overlay 和 underlay

  • overlay:叠加网络 ,搭建隧道
  • underlay:承载网络,设置路由表

overlay

OverLay其实就是一种隧道技术,VXLAN,NVGRE及STT是典型的三种隧道技术,它们都是通过隧道技术实现大二层网络。将原生态的二层数据帧报文进行封装后在通过隧道进行传输。总之,通过OverLay技术,我们在对物理网络不做任何改造的情况下,通过隧道技术在现有的物理网络上创建了一个或多个逻辑网络即虚拟网络,有效解决了物理数据中心,尤其是云数据中心存在 的诸多问题,实现了数据中心的自动化和智能化

以 flannel 为例,默认网段 10.224.1.0/16,flannel 在每个节点创建一个网卡 flannel.1,这是一个隧道,网段10.2441.0/32 - 10.244.255/32,而每个节点上的容器的 ip 为10.244.x.1/24 - 10.244.x.254/24,也就是说 flannel 默认支持最多 256 个节点,每个节点上又最多支持 256 个容器

underlay

UnderLay指的是物理网络,它由物理设备和物理链路组成。常见的物理设备有交换机、路由器、防火墙、负载均衡、入侵检测、行为管理等,这些设备通过特定的链路连接起来形成了一个传统的物理网络,这样的物理网络,我们称之为UnderLay网络

UnderLay是底层网络,负责互联互通而Overlay是基于隧道技术实现的,overlay的流量需要跑在underlay之上

资源

Pod、Deployment、Service等等,反映在Etcd中,就是一张张的表

kube-apiserver 以群组分区资源,群组化管理的api使得其可以更轻松地进行扩展,常用的api群组分为两类:

  1. 命名群组:/apis/$GROUP_NAME/$VERSION,例如 /apis/apps/v1
  2. 核心群组core:简化了路径,/api/$VERSION,即 /api/v1

打印 kube-apiserver 支持的所有资源:

kubectl api-resources

对象

资源表中的每一条数据项就是一个对象,例如 Pod表中的数据项就是Pod对象。所以资源表通常不叫Pod表、Deployment表…,而是叫做PodList、DeploymentList…

创建对象

三种方式

  1. 命令式命令:命令,全部配置通过选项指定
  2. 命令式配置文件:命令,加载配置文件
  3. 声明式配置文件:声明式命令,加载配置清单,推荐使用

配置清单:

# 配置清单的规范叫做资源规范,范例:
apiVersion: v1
kind: Pod
metadata:
	name: myPod
	labels:
		app: mypod
		release: canary
spec:													# 期望状态
	containers:
	- name: demoapp
	  image: ikubernetes/demoapp:v1.0

资源清单是yml格式,api-server会自动转成json格式

如果实际状态和期望状态有出入,控制器的控制循环就会监控到差异,然后将需要做出的更改提交到apiserver,调度器scheduler监控到apiserver中有未执行的操作,就会去找适合执行操作的node,然后提交到apiserver,kubelet监控到apiserver中有关于自己节点的操作,就会执行操作,将执行结果返回给apiserver,apiserver再更新实际状态

查看对象

外部访问

domain:6643/apis/$GROUP_NAME/$VERSION/namespaces/$NAMESPACE/$NAME/$API_RESOURCE_NAME
domain:6643/api/$VERSION/namespaces/$NAMESPACE/$NAME/$API_RESOURCE_NAME

# 范例:
domain:6643/api/v1/namespaces/default/pods/demoapp-5f7d8f9847-tjn4v

内部访问

以下三种访问方式是一样的:

# 方式一,这种方式最方便
kubectl get pods net-tes1 -o json|yaml
# 方式二
kubectl get --raw /api/v1/namespaces/default/pods/net-test1
# 方式三,这种方式适用于监控
kubectl proxy	# 搭建代理
curl 127.0.0.1:8001/api/v1/namespaces/default/pods/net-test1	# 另起一终端

注:1.20之前的版本可以直接curl 127.0.0.1:8080,并且通过--insecure-port可以修改默认的8080端口,1.20.4之后的版本取消了这种不安全的访问方式,只能通过以上方式三,先kubectl proxy代理一下,默认端口也改成了8001

The kube-apiserver ability to serve on an insecure port, deprecated since v1.10, has been removed. The insecure address flags --address and --insecure-bind-address have no effect in kube-apiserver and will be removed in v1.24. The insecure port flags --port and --insecure-port may only be set to 0 and will be removed in v1.24. (#95856, @knight42, [SIG API Machinery, Node, Testing])

$kubectl get pods net-test1 -o yaml
 apiVersion: v1
 kind: Pod
>metadata:
>spec:
>status:

以上返回的数据,每个字段表示的意义可以通过 kubectl explain 查询帮助

kubectl explain <type>.<fieldName>[.<fieldName>]
$kubectl explain pod.apiVersion
KIND:     Pod
VERSION:  v1

FIELD:    apiVersion <string>

DESCRIPTION:
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

# 范例
$kubectl explain pod.kind
$kubectl explain pod.metadata
$kubectl explain pod.spec

lease 租约

lease 是分布式系统中一个常见的概念,用于代表一个分布式租约。典型情况下,在分布式系统中需要去检测一个节点是否存活的时,就需要租约机制。

上图示例中的代码示例首先创建了一个 10s 的租约,如果创建租约后不做任何的操作,那么 10s 之后,这个租约就会自动过期。接着将 key1 和 key2 两个 key value 绑定到这个租约之上,这样当租约过期时 etcd 就会自动清理掉 key1 和 key2,使得节点 key1 和 key2 具备了超时自动删除的能力。

如果希望这个租约永不过期,需要周期性的调用 KeeyAlive 方法刷新租约。比如说需要检测分布式系统中一个进程是否存活,可以在进程中去创建一个租约,并在该进程中周期性的调用 KeepAlive 的方法。如果一切正常,该节点的租约会一致保持,如果这个进程挂掉了,最终这个租约就会自动过期。

在 etcd 中,允许将多个 key 关联在同一个 lease 之上,这个设计是非常巧妙的,可以大幅减少 lease 对象刷新带来的开销。试想一下,如果有大量的 key 都需要支持类似的租约机制,每一个 key 都需要独立的去刷新租约,这会给 etcd 带来非常大的压力。通过多个 key 绑定在同一个 lease 的模式,我们可以将超时间相似的 key 聚合在一起,从而大幅减小租约刷新的开销,在不失灵活性同时能够大幅提高 etcd 支持的使用规模。

tomcat 日志

tomcat默认的格式比较简单,包含 ip、date、method、url、status code 等信息,例如:

10.0.0.1 - - [06/Mar/2021:09:26:09 +0800] "GET / HTTP/1.1" 200 11156
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /tomcat.svg HTTP/1.1" 200 67795
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /tomcat.css HTTP/1.1" 200 5542
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /bg-nav.png HTTP/1.1" 200 1401
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /bg-middle.png HTTP/1.1" 200 1918
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /bg-upper.png HTTP/1.1" 200 3103
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /bg-button.png HTTP/1.1" 200 713 
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /asf-logo-wide.svg HTTP/1.1" 200 27235
10.0.0.1 - - [06/Mar/2021:09:26:10 +0800] "GET /favicon.ico HTTP/1.1" 200 21630
10.0.0.1 - - [06/Mar/2021:09:26:12 +0800] "GET /manager/status HTTP/1.1" 403 3446
10.0.0.1 - - [06/Mar/2021:09:26:14 +0800] "GET /manager/html HTTP/1.1" 403 3446
10.0.0.1 - - [06/Mar/2021:09:26:19 +0800] "GET / HTTP/1.1" 200 11156
10.0.0.1 - - [06/Mar/2021:09:26:19 +0800] "GET /favicon.ico HTTP/1.1" 200 21630
  1. 为了kibana能单独统计每个字段,需要日志记录成json格式,当然也可以是其他格式,只要有对应的codec插件可以解析就行

    [root@elk2-ljk conf]$pwd
    /usr/local/tomcat/conf
    [root@elk2-ljk conf]$vim server.xml		# 自定义日志格式 为json格式
    ...
            <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                   prefix="tomcat_access_log" suffix=".log"
                   pattern="{&quot;clientip&quot;:&quot;%h&quot;,&quot;ClientUser&quot;:&quot;%l&quot;,&quot;authenticated&quot;:&quot;%u&quot;,&quot;AccessTime&quot;: &quot;%t&quot;,&quot;method&quot;:&quot;%r&quot;,&quot;status&quot;:&quot;%s&quot;,&quot;SendBytes&quot;:&quot;%b&quot;,&quot;Query?string&quot;:&quot;%q&quot;,    &quot;partner&quot;:&quot;%{Referer}i&quot;,&quot;AgentVersion&quot;:&quot;%{User-Agent}i&quot;}" />
    ...
    
    # &quot; 表示双引号
    
    [root@elk2-ljk conf]$systemctl restart tomcat.service	# 重启tomcat
    # 查看日志,已经成功修改为json格式
    [root@elk2-ljk logs]$cat ../logs/tomcat_access_log.2021-03-06.log
    {"clientip":"10.0.0.1","ClientUser":"-","authenticated":"-","AccessTime":"[06/Mar/2021:12:50:56 +0800]","method":"GET / HTTP/1.1","status":"200","SendBytes":       "11156","Query?string":"","partner":"-","AgentVersion":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36"}
    {"clientip":"10.0.0.1","ClientUser":"-","authenticated":"-","AccessTime":"[06/Mar/2021:12:50:56 +0800]","method":"GET /favicon.ico HTTP/1.1","status":"200",        "SendBytes":"21630","Query?string":"","partner":"http://10.0.1.122:8080/","AgentVersion":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like  Gecko) Chrome/88.0.4324.190 Safari/537.36"}
    {"clientip":"10.0.0.1","ClientUser":"-","authenticated":"-","AccessTime":"[06/Mar/2021:12:51:00 +0800]","method":"GET / HTTP/1.1","status":"200","SendBytes":       "11156","Query?string":"","partner":"-","AgentVersion":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36"}

    百度搜索 “tomcat日志格式” 或 “apache日志格式”,下面是部分格式说明:

    %a - 远程IP地址
    %A - 本地IP地址
    %b - 发送的字节数,不包括HTTP头,或“ - ”如果没有发送字节
    %B - 发送的字节数,不包括HTTP头
    %h - 远程主机名
    %H - 请求协议
    %l (小写的L)- 远程逻辑从identd的用户名(总是返回' - ')
    %m - 请求方法
    %p - 本地端口
    %q - 查询字符串(在前面加上一个“?”如果它存在,否则是一个空字符串
    %r - 第一行的要求
    %s - 响应的HTTP状态代码
    %S - 用户会话ID
    %t - 日期和时间,在通用日志格式
    %u - 远程用户身份验证
    %U - 请求的URL路径
    %v - 本地服务器名
    %D - 处理请求的时间(以毫秒为单位)
    %T - 处理请求的时间(以秒为单位)
    %I (大写的i) - 当前请求的线程名称
    
    %{XXX}i xxx代表传入的头(HTTP Request)
    %{XXX}o xxx代表传出​​的响应头(Http Resonse)
    %{XXX}c  xxx代表特定的Cookie名
    %{XXX}r  xxx代表ServletRequest属性名
    %{XXX}s xxx代表HttpSession中的属性名
  2. logstash 配置文件:

    input {
        file {
            type => "tomcat-access-log"
            path => "/usr/local/tomcat/logs/tomcat_access_log.*.log"
            start_position => "end"
            stat_interval => 3
            codec => "json"
        }
    }
    
    output {
        if [type] == "tomcat-access-log" {
            elasticsearch { 
                hosts => ["10.0.1.121:9200"]
                index => "mytest-%{type}-%{+xxxx.ww}"
            }
        }
    }
  3. 重启logstash,注意要以root用户身份启动,否则无法采集数据

    [root@elk2-ljk conf.d]$systemctl restart logstash.service

java 日志

基于java开发的应用,都会有java日志,java日志会记录java的报错信息,但是一个报错会产生多行日志,例如:

为了方便观察,需要将一个报错的多行日志合并为一行,以elasticsearch为例:

input {
    file {
        type => "elasticsearch-java-log"
        path => "/data/elasticsearch/logs/es-cluster.log"
        start_position => "beginning"
        stat_interval => 3
        codec => multiline {
            pattern => "^\["
            negate => true
            what => "previous"
        }
    }   
}

output {
    elasticsearch { 
        hosts => ["10.0.1.121:9200"]
        index => "mytest-%{type}-%{+yyyy.MM}"
    }   
}

查看kibana:

nginx 访问日志

和采集tomcat日志类似,重点是把nginx日志修改为json格式

# 注意:log_format要写在server外面
[root@47105171233 vhost]$cat lujinkai.cn.conf 
log_format access_json '{"@timestamp":"$time_iso8601",'
'"host":"$server_addr",'
'"clientip":"$remote_addr",'
'"size":$body_bytes_sent,'
'"responsetime":$request_time,'
'"upstreamtime":"$upstream_response_time",'
'"upstreamhost":"$upstream_addr",'
'"http_host":"$host",'
'"url":"$uri",'
'"domain":"$host",'
'"xff":"$http_x_forwarded_for",'
'"referer":"$http_referer",'
'"status":"$status"}';

server {
  listen 80;
  server_name lujinkai.cn;
  access_log /data/wwwlogs/lujinkai.cn_nginx.log access_json;
  rewrite / http://blog.lujinkai.cn permanent;
}

# 日志成功转为json格式
[root@47105171233 wwwlogs]$tail -f lujinkai.cn_nginx.log 
{"@timestamp":"2021-03-06T18:20:13+08:00","host":"10.0.0.1","clientip":"113.120.245.191","size":162,"responsetime":0.000,"upstreamtime":"-","upstreamhost":"-","http_host":"lujinkai.cn","url":"/","domain":"lujinkai.cn","xff":"-","referer":"-","status":"301"}
{"@timestamp":"2021-03-06T18:20:13+08:00","host":"10.0.0.1","clientip":"113.120.245.191","size":162,"responsetime":0.000,"upstreamtime":"-","upstreamhost":"-","http_host":"lujinkai.cn","url":"/robots.txt","domain":"lujinkai.cn","xff":"-","referer":"-","status":"301"}

TCP/UDP 日志

场景:没有安装logstash的服务器(A),向安装了logstash的服务器(B)发送日志信息,这个场景不多

实现:A通过nc命令给B发送日志信息,B监听本地的对应端口,接收数据

A:客户端

[root@elk2-ljk ~]$nc 10.0.1.121 9889

B:服务端

input {
    tcp {
        port => 9889
        type => "tcplog"
        mode => "server"
    }
}

output {
    stdout {
    	codec => rubydebug
    }
}

通过 rsyslog 收集 haproxy 日志

有些设备无法安装logstash,例如 路由器、交换机,但是厂家内置了 rsyslog 功能,这里以haproxy为例

# 1. haproxy.cfg 定义haproxy的日志设备为local6
log 127.0.0.1 local6 info

# 2. rsyslog.conf
module(load="imudp")
input(type="imudp" port="514")
module(load="imtcp")
input(type="imtcp" port="514")
local6.* @@10.0.1.122

# 3. 10.0.1.122主机监听本机的udp514端口,接收日志数据
input{
    syslog {
    	type => "ststem-rsyslog"
    }
}
output{
    elasticsearch {
        hosts => ["10.0.1.123:9200"]
        index => "logstash-rsyslog-%{+YYYY.MM.dd}"
    }
}

filebeat 收集日志并写入 redis/kafka

考虑到elasticsearch的性能问题,通常不会直接往elasticsearch写日志,会加redis或kafka缓冲一下

  • 有少数场景,需要收集多个不同格式的日志,例如有的是syslog格式,有的是json格式,所有的日志都output到redis的一个list中,因为logstash的input没有条件判断,只能配置一个codec,所以logstash可以将不同的日志发送到elasticsearch的不同index,却无法对不同的日志格式配置不同的codec,这样的数据最后展示在kibana也没有意义,解决方法是在日志收集和日志缓存中间在加一个logstash(可以共用日志提取及过滤的logstash),将不同的日志转发到redis的不同list
  • 日志缓存如果用redis,需要大内存,推荐32G;如果用kafka,16G就够,因为kafka存储数据到磁盘

日志收集实战

从左向右看,当要访问 ELK 日志统计平台的时候,首先访问的是两台 nginx+keepalived 做的负载高可用,访问的地址是 keepalived 的 IP,当一台 nginx 代理服务器挂掉之后也不影响访问,然后 nginx 将请求转发到kibana,kibana 再去 elasticsearch 获取数据,elasticsearch 是两台做的集群,数据会随机保存在任意一台 elasticsearch 服务器,redis 服务器做数据的临时保存,避免 web 服务器日志量过大的时候造成的数据收集与保存不一致导致的日志丢失,可以临时保存到 redis,redis 可以是集群,然后再由 logstash 服务器在非高峰时期从 redis 持续的取出即可,另外有一台 mysql 数据库服务器,用于持久化保存特定的数据,web 服务器的日志由 filebeat 收集之后发送给另外的一台logstash,再有其写入到 redis 即可完成日志的收集,从图中可以看出,redis 服务器处于前端结合的最中间,其左右都要依赖于 redis 的正常运行,web 服务删个日志经过 filebeat 收集之后通过日志转发层的 logstash 写入到 redis 不同的 key当中,然后提取层 logstash 再从 redis 将数据提取并安按照不同的类型写入到elasticsearch 的不同 index 当中,用户最终通过 nginx 代理的 kibana 查看到收集到的日志的具体内容

通过坐标地图统计客户 IP 所在城市

https://www.elastic.co/guide/en/logstash/current/plugins-filters-geoip.html

日志写入数据库

写入数据库的目的是用于持久化保存重要数据,比如状态码、客户端 IP、客户端浏览器版本等等,用于后期按月做数据统计等

写个脚本从,定期从elasticsearch中获取数据,写入到 PostgreSQL

什么是ELK

https://www.elastic.co/cn/what-is/elk-stack

ELK 全称 ELK Stack,它的更新换代产品叫 Elastic Stack

ELK = Elasticsearch + Logstash + Kibana

  • Elasticsearch:搜索和分析引擎
  • Logstash:服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到诸如 Elasticsearch 等“存储库”中
  • Kibana:让用户在 Elasticsearch 中使用图形和图表对数据进行可视化

什么是Elasticsearch

https://www.elastic.co/cn/what-is/elasticsearch

什么是 Logstash

Logstash 是 Elastic Stack 的核心产品之一,可用来对数据进行聚合和处理,并将数据发送到 Elasticsearch。Logstash 是一个开源的服务器端数据处理管道,允许您在将数据索引到 Elasticsearch 之前同时从多个来源采集数据,并对数据进行充实和转换。

什么是 kibana

https://www.elastic.co/cn/what-is/kibana

为什么使用ELK

ELK 组件在海量日志系统的运维中,可用于解决以下主要问题:

  • 分布式日志数据统一收集,实现集中式查询和管理
  • 故障排查
  • 安全信息和事件管理
  • 报表功能

elasticsearch

基本概念

参考博客:https://www.cnblogs.com/qdhxhz/p/11448451.html

重点理解 index 和 document 这两个概念:index(索引)类似kafka的topic,oss的bucket,要尽量控制 index 的数量;index中的单条数据称为document(文档),相当于mysql表中的行

之前的版本中,索引和文档中间还有个type(类型)的概念,每个索引下可以建立多个type,document存储时需要指定index和type,因为一个index中的type并不隔离,document不能重名,所以type并没有多少意义。从7.0版本开始,一个index只能建一个名为_doc的type,8.0.0 以后将完全取消

下面是一个document的源数据:

  • _index:文档所属索引名称
  • _type:文档所属类型名
  • _id:doc主键,写入时指定,如果不指定,则系统自动生成一个唯一的UUID值
  • _version:doc版本信息,保证doc的变更能以正确的顺序执行,避免乱序造成的数据丢失
  • _seq_no:严格递增的顺序号,shard级别严格递增,保证后写入的doc的_seq_no大于先写入的doc的_seq_no
  • _primary_term:和_seq_no一样是一个整数,每当primary shard发生重新分配时,比如重启,primary选举等,_primary_term会递增1
  • found:查询的ID正确那么ture, 如果 Id 不正确,就查不到数据,found字段就是false
  • _source:文档的原始JSON数据

apt安装

elasticsearch 集群中 master 与 slave 的区别:

master:统计各节点状态信息、集群状态信息统计、索引的创建和删除、索引分配的管理、关闭节点等
slave:从 master 同步数据、等待机会成为 master

  1. apt 安装

    [root@elk2-ljk src]$dpkg -i elasticsearch-7.11.1-amd64.deb

    主要目录:

    /usr/share/elasticsearch	# 主目录
    /etc/elasticsearch			# 配置文件目录
    ...
  2. 修改hosts

    [root@elk2-ljk src]$vim /etc/hosts
    ...
    10.0.1.121 elk1-ljk.local
    10.0.1.122 elk2-ljk.local
    10.0.1.123 elk3-ljk.local
  3. 修改配置文件 elasticsearch.yml

    [root@elk2-ljk /]$grep '^[a-Z]' /etc/elasticsearch/elasticsearch.yml
    cluster.name: es-cluster            # ELK的集群名称,名称相同即属于是同一个集群
    node.name: node-2                   # 当前节点在集群内的节点名称
    path.data: /data/elasticsearch/data # ES数据保存目录
    path.logs: /data/elasticsearch/logs # ES日志保存目
    bootstrap.memory_lock: true         # 服务启动的时候锁定足够的内存,防止数据写入swap
    network.host: 10.0.1.122            # 监听本机ip
    http.port: 9200                     # 监听端口
    # 集群中node节点发现列表,最好使用hostname,这里为了方便,使用ip
    discovery.seed_hosts: ["elk1-ljk.local", "elk2-ljk.local", "elk3-ljk.local"]
    # 集群初始化那些节点可以被选举为master
    cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
    gateway.recover_after_nodes: 2 # 一个集群中的 N 个节点启动后,才允许进行数据恢复处理,默认是 1
    # 设置是否可以通过正则或者_all 删除或者关闭索引库,默认 true 表示必须需要显式指定索引库名称,生产环境建议设置为 true,删除索引库的时候必须指定,否则可能会误删索引库中的索引库
    action.destructive_requires_name: true
  4. 修改内存限制

    [root@elk2-ljk src]$vim /usr/lib/systemd/system/elasticsearch.service
    ...
    LimitMEMLOCK=infinity	# 无限制使用内存
    [root@elk2-ljk src]$vim /usr/local/elasticsearch/config/jvm.options
    -Xms2g	# 最小内存限制
    -Xmx2g	# 最大内存限制
  5. 创建数据目录并修改属主

    [root@elk2-ljk src]$mkdir -p /data/elasticsearch
    [root@elk3-ljk src]$chown -R elasticsearch:elasticsearch /data/elasticsearch
  6. 启动

    [root@elk1-ljk src]$systemctl start elasticsearch.service 	# 稍等几分钟
    
    [root@elk1-ljk ~]$curl http://10.0.1.121:9200/_cat/nodes
    10.0.1.123 13 96 0 0.14 0.32 0.22 cdhilmrstw - node-3
    10.0.1.122 28 97 0 0.01 0.02 0.02 cdhilmrstw * node-2		# master
    10.0.1.121 26 96 2 0.13 0.07 0.03 cdhilmrstw - node-1

源码编译

启动总是失败,各种报错,解决不了…

安装 elasticsearch 插件

插件是为了完成不同的功能,官方提供了一些插件但大部分是收费的,另外也有一些开发爱好者提供的插件,可以实现对 elasticsearch 集群的状态监控与管理配置等功能

head 插件

在 elasticsearch 5.x 版本以后不再支持直接安装 head 插件,而是需要通过启动一个服务方式

github 地址:https://github.com/mobz/elasticsearch-head

# git太慢,这里用迅雷下载zip包,然后上传
[root@elk1-ljk src]$unzip master.zip
[root@elk1-ljk src]$cd elasticsearch-head-master/
[root@elk1-ljk elasticsearch-head-master]$npm install grunt -save
[root@elk1-ljk elasticsearch-head-master]$npm install	# 这一步要等很久
[root@elk1-ljk elasticsearch-head-master]$npm run start	# 前台启动

# 开启跨域访问支持,每个节点都需要开启
[root@elk3-ljk ~]$vim /etc/elasticsearch/elasticsearch.yml
...
http.cors.enabled: true
http.cors.allow-origin: "*"

[root@elk2-ljk games]$systemctl restart elasticsearch.service 	# 重启elasticsearch

kopf 插件

过时的插件,只支持 elasticsearc 1.x 或 2.x 的版本

cerebro 插件

新开源的 elasticsearch 集群 web 管理程序,需要 java11 或者更高版本

github 地址:https://github.com/lmenezes/cerebro

[root@elk2-ljk src]$unzip cerebro-0.9.3.zip
[root@elk2-ljk src]$cd cerebro-0.9.3/
[root@elk2-ljk cerebro-0.9.3]$vim conf/application.conf
...
# host列表
hosts = [ 
  {
    host = "http://10.0.1.122:9200"
    name = "es-cluster1"  # host的名称,如果有多个elasticsearch集群,可以用这个name区分
  #  headers-whitelist = [ "x-proxy-user", "x-proxy-roles", "X-Forwarded-For" ]
  }    
]

[root@elk2-ljk cerebro-0.9.3]$./bin/cerebro 	# 前台启动

监控 elasticsearch 集群状态

[root@elk3-ljk ~]$curl http://10.0.1.122:9200/_cluster/health?pretty=true
{
  "cluster_name" : "es-cluster",
  "status" : "green",	# green:运行正常、yellow:副本丢失、red:主分片丢失
  "timed_out" : false,
  "number_of_nodes" : 3,
  "number_of_data_nodes" : 3,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}

如果是单节点,status会显示yellow,设置副本数为0即可解决:

[root@elk1-ljk ~]$curl -X PUT "10.0.1.121:9200/_settings" -H 'Content-Type: application/json' -d' {"number_of_replicas":0}'
{"acknowledged":true}

或者:

zabbix 添加监控

logstash

官方参考文档:https://www.elastic.co/guide/en/logstash/current/index.html

logstash是一个具有3个阶段的处理管道:输入 –> 过滤器 –> 输出 

输入生成事件,过滤器修改数据(日志),输出将数据(日志)发送到其他地方

安装

logstash 依赖 java,可以自己配置 java 环境,如果不配置,logstash 会使用其自带的 openjdk

[root@elk2-ljk src]$dpkg -i logstash-7.11.1-amd64.deb

# 修改启动用户为root,不然因为权限问题,后面会出现各种莫名其妙的错误,有些根本找不到报错
[root@elk2-ljk conf.d]$vim /etc/systemd/system/logstash.service
...
User=root
Group=root
...

命令

[root@elk2-ljk bin]$./logstash --help
  • -n:node name,就是节点的hostname,例如:elk2-ljk.local

  • -f:从特定的文件或目录加载logstash配置。如果给定一个目录,该目录中的所有文件将按字典顺序合并,然后作为单个配置文件进行解析。您还可以指定通配符(globs),任何匹配的文件将按照上面描述的顺序加载

  • -e:从命令行加载logstash配置,一般不用

  • -t:检查配置文件是否合法,配合-f使用,-f指定配置文件,-t检查

    # 示例:检查test.conf的合法性
    $/usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/test.conf -t

插件

logstash 的输入和输出都依赖插件

不同的插件使用不同的配置,但是所有输入插件都支持以下配置选项:

配置项 类型 说明
add_field hash Add a field to an event
codec codec 用于输入数据的编解码器。输入编解码器是一种方便的方法,可以在数据进入输入之前对其进行解码,而不需要在Logstash管道中使用单独的过滤器
enable_metric boolean Disable or enable metric logging for this specific plugin instance by default we record all the metrics we can, but you can disable metrics collection for a specific plugin.
id string 唯一的ID,如果没有指定,logstash会自动生成一个,尤其是多个相同类型的插件时,强烈建议配置此项,例如有多个file输入,应当配置此项防止混淆
tags array Add any number of arbitrary tags to your event.
This can help with processing later.
type string 类型,例如收集 /var/log/syslog 日志,type可以设置为 “system”;收集网站日志,type可以设置为 “web”。
此项用的较多,一般会根据判断 type 值来进行输出或过滤

所有输出插件都支持以下配置选项:

配置项 类型 说明
codec codec 用于输出数据的编解码器。输出编解码器是一种方便的方法,可以在数据离开输出之前对数据进行编码,而不需要在Logstash管道中使用单独的过滤器
enable_metric boolean Disable or enable metric logging for this specific plugin instance. By default we record all the metrics we can, but you can disable metrics collection for a specific plugin.
id string 唯一的ID,如果没有指定,logstash会自动生成一个,尤其是多个相同类型的插件时,强烈建议配置此项,例如有多个file输出,应当配置此项防止混淆

可以看到input和output插件都支持codec配置项,input的codec根据被采集的日志文件确定,如果日志是json格式,则input插件的codec应当设置为json;而output的codec根据数据库确定,如果是elasticsearch,codec保持默认的rubydebug即可,如果是kafka,codec应该设置为json

input插件

stdin

标准输入

file

日志输出到文件

Setting 说明 备注
path 日志路径 必需
start_position 从文件的开头或者结尾开始采集数据 ["beginning", "end"]
stat_interval 日志收集的时间间隔 每个input文件都生成一个 .sincedb_xxxxx 文件,这个文件中记录了上次收集日志位置,下次从记录的位置继续收集

tcp

Setting 说明 备注
host 当mode是serverhost是监听的地址;
当mode是clienthost是要连接的地址
默认0.0.0.0
mode server:监听客户端连接;
client:连接到服务器;
[“server”, “client”]
port 监听的端口或要连接的端口 必需

kafka

Setting 说明 备注
bootstrap_servers host1:port1,host2:port2
topics 要订阅的topic列表,默认为[“logstash”]
decorate_events 是否添加一个 kafka元数据,包含以下信息:
topic、consumer_group、partition、offset、key
布尔值
codec 设置为 json

output插件

output

标准输出

elasticsearch

redis

Setting 说明 备注
key list(列表)名,或者channel(频道)名,
至于是哪个取决于data_type
data_type 如果data_typelist,将数据push到list;
如果data_type为channel,将数据发布到channel;
[“list”, “channel”]
host redis主机列表,可以是hostname或者ip地址 数组
port redis服务端口,默认 6379
db 数据库编号,默认0
password 身份认证,默认不认证 不建议设置密码

写个脚本统计redis的key数量,使用zabbix-agent定时执行脚本,一旦key超过某个数量,就增加logstash数量,从而加快从redis中取数据的速度

kafka

Setting 说明 备注
bootstrap_servers host1:port1,host2:port2
topic_id 主题
codec 设置为 json
batch_size The producer will attempt to batch records together into fewer requests whenever multiple records are being sent to the same partition. This helps performance on both the client and the server. This configuration controls the default batch size in bytes. 默认 16384

codec插件

multiline

合并多行,比如java的一个报错,日志中会记录多行,为了方便查看,应该将一个报错的多行日志合并成一行

Setting 说明 备注
pattern 正则匹配 必需
negate 匹配成功或失败,就开始多行合并 布尔值
what 如果模式匹配,向前多行合并,还是向后多行合并 必需,["previous", "next"]

配置

配置:https://www.elastic.co/guide/en/logstash/current/configuration.html
配置文件结构:https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html
配置文件语法:https://www.elastic.co/guide/en/logstash/current/event-dependent-configuration.html
使用环境变量:https://www.elastic.co/guide/en/logstash/current/environment-variables.html
配置文件示例:https://www.elastic.co/guide/en/logstash/current/config-examples.html
数据发送到es:https://www.elastic.co/guide/en/logstash/current/connecting-to-cloud.html

input {
  ...
}

filter {
  ...
}

output {
  ...
}

多配置文件

https://www.elastic.co/guide/en/logstash/current/multiple-pipelines.html
https://elasticstack.blog.csdn.net/article/details/100995868

示例:

# 定义了两个管道
[root@elk2-ljk logstash]$cat pipelines.yml
- pipeline.id: syslog
  path.config: "/etc/logstash/conf.d/syslog.conf"
- pipeline.id: tomcat
  path.config: "/etc/logstash/conf.d/tomcat.conf"
  
# syslog.conf 和 tomcat.conf,没有使用条件判断语句做输出判断
[root@elk2-ljk conf.d]$cat syslog.conf 
input {
    file {
        type => "syslog"
        path => "/var/log/syslog"
        start_position => "end"
        stat_interval => 3
    }   
}

output {
    if [type] == "syslog" {
        elasticsearch { 
            hosts => ["10.0.1.121:9200"]
            index => "mytest-%{type}-%{+xxxx.ww}"
        }
    }
}
[root@elk2-ljk conf.d]$cat tomcat.conf 
input {
    file {
        type => "tomcat-access-log"
        path => "/usr/local/tomcat/logs/tomcat_access_log.*.log"
        start_position => "end"
        stat_interval => 3
    }   
}

output {
    elasticsearch { 
        hosts => ["10.0.1.121:9200"]
        index => "mytest-%{type}-%{+xxxx.ww}"
    }   
}

# 启动,注意一定要以为root用户启动
[root@elk2-ljk conf.d]$systemctl restart logstash.service

效果:

测试

标准输入输出

[root@elk2-ljk conf.d]$cat /etc/logstash/conf.d/test.conf 
input {
  stdin {}
}

output {
  stdout {}
}
[root@elk2-ljk conf.d]$/usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/test.conf
...		# 启动得等一会
[INFO ] 2021-03-04 18:10:06.020 [Api Webserver] agent - Successfully started Logstash API endpoint {:port=>9600}
hello word	# 标准输入
{
    "@timestamp" => 2021-03-04T10:10:15.528Z,	# 当前事件的发生时间
          "host" => "elk2-ljk.local",			# 标记事件发生的节点
      "@version" => "1",						# 事件版本号,一个事件就是一个 ruby 对象
       "message" => "hello word"				# 息的具体内容
}

输出到文件

# 1. 修改被采集日志文件权限,至少让logstash用户可以读
[root@elk2-ljk conf.d]$chmod o+r /var/log/syslog
[root@elk2-ljk conf.d]$chmod o+r /var/log/auth.log

# 2. 编写配置文件,采集多个日志文件,输出到不同的文件
[root@elk2-ljk conf.d]$vim system-log.conf 
input {
    file {
        type => "syslog"
        path => "/var/log/syslog"
        start_position => "end"
        stat_interval => 5
    }
    file {
    	type => "authlog"
        path => "/var/log/auth.log"
        start_position => "end"
        stat_interval => 5
    }
}
output {
    if [type] == "syslog" {
        file {
            path => "/tmp/%{type}.%{+yyyy.MM.dd}"
        }
    }
    if [type] == "authlog" {
        file {
            path => "/tmp/%{type}.%{+yyyy.MM.dd}"
        }
    }
}

# 3. 检查配置文件是否合法
[root@elk2-ljk conf.d]$/usr/share/logstash/bin/logstash -f ./system-log.conf -t

# 4. 重启logstash.service
[root@elk2-ljk conf.d]$systemctl restart logstash.service

# 5. 观察输出文件
[root@elk2-ljk conf.d]$tail -f /tmp/syslog.2021.03.05
...
[root@elk2-ljk conf.d]$tail -f /tmp/authlog.2021.03.05
...

时间格式参考:http://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html

输出到 elasticsearch

elasticsearch输出插件至少指定 hostsindexhosts可以指定hosts列表

input {
    file {
        type => "syslog"
        path => "/var/log/syslog"
        start_position => "end"
        stat_interval => 3
    }
}

output {
  elasticsearch { 
        hosts => ["10.0.1.121:9200"]
        index => "mytest-%{type}-%{+xxxx.ww}"
  }
}

kibana

开源的数据分析和可视化平台,可以 对 Elasticsearch 索引中的数据进行搜索、查看、交互操作,可以很方便的利用图表、表格及地图对数据进行多元化的分析和呈现

安装

[root@elk1-ljk src]$tar zxf kibana-7.11.1-linux-x86_64.tar.gz
[root@elk1-ljk src]$mv kibana-7.11.1-linux-x86_64 /usr/local/kibana
[root@elk1-ljk src]$cd /usr/local/kibana
[root@elk1-ljk kibana]$grep '^[a-Z]' config/kibana.yml 	# 修改以下配置项
server.port: 5601
server.host: "10.0.1.121"
elasticsearch.hosts: ["http://10.0.1.121:9200"]
i18n.locale: "zh-CN"

[root@elk1-ljk kibana]$./bin/kibana --allow-root	# 启动
# nohup ./bin/kibana --allow-root >/dev/null 2>&1 &		# 后台启动

因为笔记本的性能问题,将elasticsearch集群缩减为elasticsearch单节点,这导致kibana无法连接到elasticsearch,启动失败。解决办法:将elasticsearch的数据目录清空,然后重启

[root@elk1-ljk data]$systemctl stop elasticsearch.service 	# 停止elasticsearch
[root@elk1-ljk data]$ls /data/elasticsearch/
data  logs
[root@elk1-ljk data]$rm -rf /data/elasticsearch/*	# 清空数据目录
[root@elk1-ljk data]$systemctl start elasticsearch.service	# 重新启动elasticsearch
[root@elk1-ljk kibana]$./bin/kibana --allow-root	# 再次启动kibana

查看状态

kibana 画图功能详解

添加一个仪表盘

Beats

https://www.elastic.co/cn/beats/

logstash基于java,资源消耗很大,容器等场景,大多跑的都是轻量级的服务,没有必要安装logstash,就可以用beats代替logstash做日志收集,beats基于go,性能更强,资源占用更低,但是功能也相对简单

beats是一个系列,具体包含以下类型的采集器:

  • filebeat:轻量型日志采集器,最常用
  • metricbeat:轻量型指标采集器,获取系统级的 CPU 使用率、内存、文件系统、磁盘 IO 和网络 IO 统计数据,还可针对系统上的每个进程获得与 top 命令类似的统计数据
  • heartbeat:面向运行状态监测的轻量型采集器,通过 ICMP、TCP 和 HTTP 进行 ping 检测主机、网站可用性
  • packetbeat:轻量型网络数据采集器
  • winlogbeat:轻量型 Windows 事件日志采集器
  • auditbeat:轻量型审计日志采集器
  • functionbeat:面向云端数据的无服务器采集器

除了 filebeat,其他的beat可以用zabbix替代

filebeat

官方文档:https://www.elastic.co/guide/en/beats/filebeat/current/index.html

配置:只需要配置input和output

# ============================== Filebeat inputs ==================
filebeat.inputs:

- type: log							# 日志type
  enabled: false					# 是否启动
  paths:							# 被采集日志,可以写多个
    - /var/log/*.log
  exclude_lines: ['^DBG']			# 不采集日志中的哪些行
  include_lines: ['^ERR', '^WARN']	# 只采集日志中的哪些行
  exclude_files: ['.gz$']			# 从paths的匹配中排除哪些文件
  fields:							# 自定义字段,可以定义多个,后面用来做条件判断
    level: debug
    review: 1
  multiline.pattern: ^\[			# 合并多行,匹配规则
  multiline.negate: false			# 合并多行,配皮成功或失败执行合并
  multiline.match: after			# 合并多行,向前合并还是向后合并

# ============================== Filebeat modules ==============================

filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: false
  reload.period: 10s

# ======================= Elasticsearch template setting =======================

setup.template.settings:
  index.number_of_shards: 1
  index.codec: best_compression
  _source.enabled: false

# ================================== General ===================================

#name:
#tags: ["service-X", "web-tier"]
#fields:
#  env: staging

# ================================= Dashboards =================================

#setup.dashboards.enabled: false
#setup.dashboards.url:

# =================================== Kibana ===================================

setup.kibana:
  #host: "localhost:5601"
  #space.id:

# =============================== Elastic Cloud ================================

#cloud.id:
#cloud.auth:

# ================================== Outputs ===================================
# 不同的output,有不同的配置,但是没有条件判断功能,无法根据不同的input使用不用的output
# ---------------------------- Elasticsearch Output ----------------------------
#output.elasticsearch:
  #hosts: ["localhost:9200"]
  #protocol: "https"
  #api_key: "id:api_key"
  #username: "elastic"
  #password: "changeme"

# ------------------------------ Logstash Output -------------------------------
#output.logstash:
  #hosts: ["localhost:5044"]
  #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
  #ssl.certificate: "/etc/pki/client/cert.pem"
  #ssl.key: "/etc/pki/client/cert.key"

# ================================= Processors =================================
processors:
  - add_host_metadata:
      when.not.contains.tags: forwarded
  - add_cloud_metadata: ~
  - add_docker_metadata: ~
  - add_kubernetes_metadata: ~

# ================================== Logging ===================================

#logging.level: debug
#logging.selectors: ["*"]

# ============================= X-Pack Monitoring ==============================

#monitoring.enabled: false
#monitoring.cluster_uuid:
#monitoring.elasticsearch:

# ============================== Instrumentation ===============================

#instrumentation:
    #enabled: false
    #environment: ""
    #hosts:
    #  - http://localhost:8200
    #api_key:
    #secret_token:

# ================================= Migration ==================================

#migration.6_to_7.enabled: true

示例:

metricbeat

heartbeat

SonarQube 是一个用于代码质量管理的开放平台,通过插件机制,SonarQube 可以集成不同的测试
工具,代码分析工具,以及持续集成工具,例如Hudson/Jenkins 等

官网:https://www.sonarqube.org/

部署SonarQube

略…

jenkins服务器部署扫描器 sonar-scanner

官方文档: https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/

部署sonar-scanner

顾名思义,扫描器的具体工作就是扫描代码,sonarqube通过调用扫描器sonar-scanner进行代码质量分析

下载地址: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/

[root@jenkins src]$unzip sonar-scanner-cli-4.6.0.2311.zip
[root@jenkins src]$mv sonar-scanner-4.6.0.2311/ /usr/local/sonar-scanner
[root@jenkins src]$vim /usr/local/sonar-scanner/conf/sonar-scanner.properties
sonar.host.url=http://10.0.1.102:9000	# 指向sonarqube服务器的地址
sonar.sourceEncoding=UTF-8		# Default source code encoding

准备测试代码

[root@jenkins src]$unzip sonar-examples-master.zip ^C
[root@jenkins src]$cd sonar-examples-master/projects/languages/php/php-sonar-runner
[root@jenkins php-sonar-runner]$ll
total 24
drwxr-xr-x 3 root root 4096 Mar  2 23:30 ./
drwxr-xr-x 4 root root 4096 Jul 25  2016 ../
-rw-r--r-- 1 root root  453 Jul 25  2016 README.md
-rw-r--r-- 1 root root  331 Jul 25  2016 sonar-project.properties
drwxr-xr-x 2 root root 4096 Jul 25  2016 src/
-rw-r--r-- 1 root root  272 Jul 25  2016 validation.txt
[root@jenkins php-sonar-runner]$cat sonar-project.properties 	# 确保有这个文件
# Required metadata
sonar.projectKey=org.sonarqube:php-simple-sq-scanner
sonar.projectName=PHP :: Simple Project :: SonarQube Scanner
sonar.projectVersion=1.0

# Comma-separated paths to directories with sources (required)
sonar.sources=src

# Language
sonar.language=php

# Encoding of the source files
sonar.sourceEncoding=UTF-8

在源代码目录执行扫描

在 sonar-project.properties 这个文件的目录下,执行 sonar-scanner 即可:

[root@jenkins php-sonar-runner]$ll
total 24
drwxr-xr-x 3 root root 4096 Mar  2 23:30 ./
drwxr-xr-x 4 root root 4096 Jul 25  2016 ../
-rw-r--r-- 1 root root  453 Jul 25  2016 README.md
-rw-r--r-- 1 root root  331 Jul 25  2016 sonar-project.properties
drwxr-xr-x 2 root root 4096 Jul 25  2016 src/	# 代码
-rw-r--r-- 1 root root  272 Jul 25  2016 validation.txt
[root@jenkins php-sonar-runner]$
[root@jenkins php-sonar-runner]$/usr/local/sonar-scanner/bin/sonar-scanner	# 测试
INFO: Scanner configuration file: /usr/local/sonar-scanner/conf/sonar-scanner.properties
INFO: Project root configuration file: /usr/local/src/sonar-examples-master/projects/languages/php/php-sonar-runner/sonar-project.properties
INFO: SonarScanner 4.6.0.2311
INFO: Java 11.0.10 Oracle Corporation (64-bit)
INFO: Linux 4.15.0-136-generic amd64
INFO: User cache: /root/.sonar/cache
INFO: Scanner configuration file: /usr/local/sonar-scanner/conf/sonar-scanner.properties
INFO: Project root configuration file: /usr/local/src/sonar-examples-master/projects/languages/php/php-sonar-runner/sonar-project.properties
INFO: Analyzing on SonarQube server 7.9.5
INFO: Default locale: "en_US", source code encoding: "UTF-8"
INFO: Load global settings
INFO: Load global settings (done) | time=225ms
INFO: Server id: 3B6AA649-AXfye5RyEWrAjeeRmPxd
INFO: User cache: /root/.sonar/cache
INFO: Load/download plugins
INFO: Load plugins index
INFO: Load plugins index (done) | time=126ms
INFO: Plugin [l10nzh] defines 'l10nen' as base plugin. This metadata can be removed from manifest of l10n plugins since version 5.2.
INFO: Load/download plugins (done) | time=3633ms
INFO: Process project properties
INFO: Execute project builders
INFO: Execute project builders (done) | time=18ms
INFO: Project key: org.sonarqube:php-simple-sq-scanner
INFO: Base dir: /usr/local/src/sonar-examples-master/projects/languages/php/php-sonar-runner
INFO: Working dir: /usr/local/src/sonar-examples-master/projects/languages/php/php-sonar-runner/.scannerwork
INFO: Load project settings for component key: 'org.sonarqube:php-simple-sq-scanner'
INFO: Load quality profiles
INFO: Load quality profiles (done) | time=293ms
INFO: Load active rules
INFO: Load active rules (done) | time=3061ms
WARN: SCM provider autodetection failed. Please use "sonar.scm.provider" to define SCM of your project, or disable the SCM Sensor in the project settings.
INFO: Indexing files...
INFO: Project configuration:
INFO: Load project repositories
INFO: Load project repositories (done) | time=19ms
INFO: 1 file indexed
INFO: Quality profile for php: Sonar way
INFO: ------------- Run sensors on module PHP :: Simple Project :: SonarQube Scanner
INFO: Load metrics repository
INFO: Load metrics repository (done) | time=134ms
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/root/.sonar/cache/866bb1adbf016ea515620f1aaa15ec53/sonar-javascript-plugin.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
INFO: Sensor JaCoCo XML Report Importer [jacoco]
INFO: Sensor JaCoCo XML Report Importer [jacoco] (done) | time=12ms
INFO: Sensor JavaXmlSensor [java]
INFO: Sensor JavaXmlSensor [java] (done) | time=7ms
INFO: Sensor HTML [web]
INFO: Sensor HTML [web] (done) | time=144ms
INFO: Sensor PHP sensor [php]
INFO: 1 source files to be analyzed
INFO: 1/1 source files have been analyzed
INFO: No PHPUnit test report provided (see 'sonar.php.tests.reportPath' property)
INFO: No PHPUnit coverage reports provided (see 'sonar.php.coverage.reportPaths' property)
INFO: Sensor PHP sensor [php] (done) | time=1652ms
INFO: Sensor Analyzer for "php.ini" files [php]
INFO: Sensor Analyzer for "php.ini" files [php] (done) | time=26ms
INFO: ------------- Run sensors on project
INFO: Sensor Zero Coverage Sensor
INFO: Sensor Zero Coverage Sensor (done) | time=21ms
INFO: No SCM system was detected. You can use the 'sonar.scm.provider' property to explicitly specify it.
INFO: Calculating CPD for 1 file
INFO: CPD calculation finished
INFO: Analysis report generated in 189ms, dir size=83 KB
INFO: Analysis report compressed in 17ms, zip size=14 KB
INFO: Analysis report uploaded in 1437ms
INFO: ANALYSIS SUCCESSFUL, you can browse http://10.0.1.102:9000/dashboard?id=org.sonarqube%3Aphp-simple-sq-scanner
INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
INFO: More about the report processing at http://10.0.1.102:9000/api/ce/task?id=AXfzlFbUEMwg_dNR3M3w
INFO: Analysis total time: 13.302 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 21.257s
INFO: Final Memory: 8M/40M
INFO: ------------------------------------------------------------------------
[root@jenkins php-sonar-runner]$

web 看测试结果:

jenkins 执行代码扫描

上面是命令行执行 sonar-scanner 命令进行测试,可以结合 jenkins 进行测试,无非就是将命令写到脚本里,让jenkins 自动执行

官方网站: https://jenkins.io/zh/

Jenkins是开源CI&CD软件领导者, 提供超过1000个插件来支持构建、部署、自动化, 满足任何项目的需要。

部署jenkins

Jenkins 支持各种运行方式,可通过系统包、Docker 或者通过一个独立的 Java 程序

安装 JDK

Jenkins 基于 JAVA 实现,安装 Jenkins 前需要先安装 JDK

[root@jenkins ~]$java -version
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)

安装 jenkins

$wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/debian-stable/jenkins_2.263.4_all.deb	# 清华源下载dep包
[root@jenkins src]$apt install daemon	# 依赖的包,如果没有,会报错,所以提前装上
[root@jenkins src]$ln -s /usr/local/jdk/bin/java /usr/bin/java # Jenkins只会在/bin:/usr/bin:/sbin:/usr/sbin找java,所以设置$PATH不好使,得做个软连接

[root@jenkins src]$dpkg -i jenkins_2.263.4_all.deb	# 安装

主要文件:

/etc/default/jenkins
/etc/init.d/jenkins
/etc/logrotate.d/jenkins
/usr/share/jenkins/jenkins.war	# 实际上就是启动这个war包
/var/cache/jenkins/
/var/lib/jenkins/
/var/log/jenkins/

修改 jenkins 服务的用户

默认jenkins服务使用 jenkins 帐号启动,将文件复制到生产服务器可能会遇到权限问题,需要先su到对应的用户,这里学习环境为了方便,修改为root用户,注意:工作中还是使用 jenkins 普通用户

[root@jenkins src]$vim /etc/default/jenkins 
#JENKINS_USER=$NAME
#JENKINS_GROUP=$GROUP
JENKINS_USER=root		# 直接修改为root,不要修改$NAME变量
JENKINS_GROUP=root

访问jenkins 页面

创建管理员用户

先创建用户,然后使用刚创建的用户登录

安装插件

Jenkins 是完全插件化的服务,我们想实现什么功能,不是改配置文件,而是下载插件

修改更新源为国内清华源:

[root@jenkins src]$cat /var/lib/jenkins/hudson.model.UpdateCenter.xml
<?xml version='1.1' encoding='UTF-8'?>
<sites>
  <site>
    <id>default</id>
    <url>https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json</url>
  </site>
</sites>
[root@jenkins src]$systemctl restart jenkins.service

有以下几种安装插件的方式:

  • 在线安装:官网,也是默认的方式,比较慢

  • 在线安装:清华大学镜像源,sed或者编辑器把 update-center.json 中的Jenkins网址替换为清华源

    # 将:
    https://updates.jenkins.io/download/plugins/warnings/5.0.1/warnings.hpi
    # 替换为:
    https://mirrors-i.tuna.tsinghua.edu.cn/jenkins/plugins/warnings/5.0.1/warnings.hpi
  • 离线安装:手动下载插件,然后放到 /var/lib/jenkins/plugins/,重启 Jenkins 即可

  • 通过web界面安装

搜索需要 gitlab(和gitlab相连)和 Blue Ocean(显示信息更加好看)的相关插件并安装

配置 jenkins 权限管理

默认jenkins用户可以执行所有操作,为了更好的分层控制,可以实现基于角色的权限管理,先创建角色和用户,给角色授权,然后把用户管理到角色

安装插件

安装插件:Role-based Authorization Strategy

如果直接下载失败,可以直接清华源下载,将插件放在 /var/lib/jenkins/plugins 目录下

创建新用户

新建 xiaoming 和 xiaogang 两个用户:

更改认证方式

新建任务

通常选择“构建一个自由风格的软件项目”,新建四个任务:test1-job1、test1-job2、test2-job1、test2-job2

创建角色并对角色分配权限

  1. 创建全局角色
    创建全局读角色,只有读权限

  2. 创建 item 角色
    创建两个 item 角色,test1-role、test2-role,通过正则匹配分配任务

    ![](https://img.lujinkai.cn/blog/ljk/1614438723442.png

将用户关联到角色

测试普通用户登录

xiaogang 用户只能看到 test1.* 匹配的项目:test1-job1、test1-job2

xiaoming 用户只能看到 test2.*匹配的项目:test2-job1、test2-job2

jenkins 邮箱配置

邮件发送到组邮件地址,组邮件会转发到组内所有人

配置jenkins到gitlab非交互式拉取代码

需要配置 ssh key (公钥)和 凭据(私钥)

配置 ssh key

实现 jenkins 服务器到 gitlab 服务器的基于密钥的验证,可以让 jenkins 连接到 gitlab 执行操作

  1. 在 jenkins 服务上生成 ssh key
  2. 在 gitlab 服务器上添加上面生成的 ssh key
  3. 在 jenkins 服务器上测试 ssh key

配置凭据

凭据就是私钥,clone项目的时候,可选择不同的凭据(私钥),只要他在gitlab上配置了key(公钥),如果不指定,就以当前用户的私钥作为凭据去clone项目

管理凭据:系统管理 -> 安全 -> Manage Credentials

构建触发器

构建触发器(webhook),有的人称为钩子,实际上是一个HTTP回调,其用于在开发人员向 gitlab 提交代码后能够触发 jenkins 自动执行代码构建操作

以下为新建一个开发分支,只有在开发人员向开发(develop)分支提交代码的时候才会触发代码构建,而向主分支提交的代码不会自动构建,需要运维人员手动部署代码到生产环境

生产中千万不要用,测试一般也不用,都是由开发登陆自己的账号,只能看到自己的job,然后开发自己手动部署

项目关联

用于多个 job 相互关联,需要串行执行多个 job 的场景,不过这个操作基本不用

例如:克隆代码、编译代码、停止tomcat、部署代码、启动tomcat 这些操作使用不同的job执行,这些job需要串行执行

视图

job 太多需要分类

视图可用于归档 job 进行分组显示,比如将一个业务的 job 放在一个视图显示,最常用的是列表视图

jenkins 分布式

在众多Job的场景下,单台jenkins master同时执行代码clone、编译、打包及构建,其性能可能会出现瓶颈从而会影响代码部署效率,影响jenkins官方提供了jenkins分布式构建,将众多job分散运行到不同的jenkins slave节点,大幅提高并行job的处理能力

配置slave节点环境

slave节点需要配置与master一样的基础运行环境,另外也要创建与master相同的数据目录,因为脚本中调用的路径只有相对于master的一个路径,此路径在master与各node节点必须保持一致

# 配置java环境
[root@jenkins-slave1 ~]$java -version
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)

[root@jenkins-slave1 ~]$ln -s /usr/local/jdk/bin/java /usr/bin/java

# 创建数据目录
[root@jenkins-slave1 ~]$mkdir -p /var/lib/jenkins

添加slave节点

可以限制项目只能在指定的节点中执行:

流水线 pipline

流水线 pipline 是 Jenkins 中的头等公民,官方介绍;https://www.jenkins.io/zh/doc/book/pipeline/

本质上,Jenkins 是一个自动化引擎,它支持许多自动模式。 流水线向Jenkins中添加了一组强大的工具, 支持用例 简单的持续集成到全面的CD流水线。通过对一系列的相关任务进行建模, 用户可以利用流水线的很多特性:

  • 代码:流水线是在代码中实现的,通常会检查到源代码控制,使团队有编辑,审查和迭代他们的交付流水线的能力
  • 可持续性:jenkins的重启或者中断后不影响已经执行的Pipline Job
  • 支持暂停:pipline可以选择停止并等待人工输入或批准后再继续执行
  • 可扩展:通过groovy的编程更容易的扩展插件
  • 并行执行:通过groovy脚本可以实现step,stage间的并行执行,和更复杂的相互依赖关系

流水线概念

流水线 pipeline

流水线是用户定义的一个CD流水线模型 。流水线的代码定义了整个的构建过程, 他通常包括构建, 测试和交付应用程序的阶段

节点 node

每个 node 都是一个 jenkins 节点,可以是 jenkins master 也可以是 jenkins agent,node 是执行 step 的具体服务器

阶段 stage

一个 pipline 可以划分为若干个 stage,每个 stage 都是一个操作,比如 clone 代码、代码编译、代码测试和代码部署,阶段是一个逻辑分组,可以跨多个 node 执行

步骤 step

step 是 jenkins pipline 最基本的操作单元,从在服务器创建目录到构建容器镜像,由各类 Jenkins 插件提供实现,例如: sh “make”

流水线语法概述

对 Jenkins 流水线的定义被写在一个文本文件中: Jenkinsfile,该文件可以被提交到项目的源代码的控制仓库。 这是”流水线即代码”的基础;将CD 流水线作为应用程序的一部分,像其他代码一样进行版本化和审查。

Jenkinsfile 能使用两种语法进行编写,声明式和脚本化。

声明式和脚本化的流水线从根本上是不同的。 声明式流水线的是 Jenkins 流水线更近的特性

声明式流水线基础

在声明式流水线语法中, pipeline 块定义了整个流水线中完成的所有的工作。

示例:

Jenkinsfile (Declarative Pipeline)
pipeline { 
    agent any 
    stages {
        stage('Build') { 
            steps { 
                sh 'make' 
            }
        }
        stage('Test'){
            steps {
                sh 'make check'
                junit 'reports/**/*.xml' 
            }
        }
        stage('Deploy') {
            steps {
                sh 'make publish'
            }
        }
    }
}
  • pipeline 是声明式流水线的一种特定语法,他定义了包含执行整个流水线的所有内容和指令的 “block” 。
  • agent是声明式流水线的一种特定语法,它指示 Jenkins 为整个流水线分配一个执行器 (在节点上)和工作区。
  • stage 是一个描述 stage of this Pipeline的语法块。在 Pipeline syntax 页面阅读更多有关声明式流水线语法的stage块的信息。如 above所述, 在脚本化流水线语法中,stage 块是可选的。
  • steps 是声明式流水线的一种特定语法,它描述了在这个 stage 中要运行的步骤。
  • sh 是一个执行给定的shell命令的流水线 step (由 Pipeline: Nodes and Processes plugin提供) 。
  • junit 是另一个聚合测试报告的流水线 step (由 JUnit plugin提供)。
  • node 是脚本化流水线的一种特定语法,它指示 Jenkins 在任何可用的代理/节点上执行流水线 (和包含在其中的任何阶段)这实际上等效于 声明式流水线特定语法的agent

脚本化流水线基础

pipline 语法

官方文档:https://www.jenkins.io/zh/doc/book/pipeline/syntax/

pipline job

创建pipline job

测试简单pipline job运行

node {
    stage('clone 代码') {
        echo '代码 clone'
    }
    stage('代码构建') {
        echo '代码构建'
    }
    stage('代码测试') {
        echo '代码测试'
    }
    stage('代码部署') {
        echo '代码部署'
    }
}

立即构建,查看输出信息:

自动生成拉取代码的pipline脚本

案例

说明

master分支:稳定的分支
develop分支:开发分支

lujinkai:开发人员分支
xiaoming:开发人员分支
...

master分支是上线的代码,develop是开发中的代码,lujinkai、xiaoming等是开发人员分支,开发在自己的分支里面写代码,然后下班前提交、合并到develop分支,等项目开发、测试完,最后再合并到master分支,然后上线

常用命令

[root@kafka2 testproject]$git branch		# 查看本地分支
* master	# 分支前标 * 表示当前处于的分支

[root@kafka2 testproject]$git branch -a		# 查看所有分支
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/ludongsheng
  remotes/origin/master
  
git status      # 查看工作区的状态
git branch test # 创建test分支
git merge test  # 将test分支合并到当前分支
git log         # 查看操作日志
git reflog      # 获取每次提交的ID,可以使用--hard根据提交的ID进行版本回退

vim .gitignore          	# 定义忽略文件,即不放在仓库的文件

git reset --hard HEAD^^ 	# git版本回滚, HEAD为当前版本,加一个^为上一个,^^为上上一个版本

git reset --hard 5ae4b06 	# 回退到指定id的版本

git checkout -b develop 	# 创建并切换到一个新分支
git checkout develop    	# 切换分支

git clone -b 分支名 仓库地址	# clone指定分支的代码

流程

  1. 在 gitlab 创建个人分支 lujinkai

  2. 克隆 master 分支到本地

    [root@dev ~]$git clone git@gitlab.ljk.local:testgroup/testproject.git
  3. 切换到自己分支

    [root@dev ~]$git checkout lujinkai
  4. 将develop分支拉下来和个人分支合并

    [root@dev ~]$git pull origin develop
    
    # git pull <远程主机名> <远程分支名>:<本地分支名>
    # 如果与当前分支合并,则冒号后面的部分可以省略

    以上命令是把远程分支develop拉下来,然后合并到当前分支lujinkai,或者可以拆分成以下步骤:

    git checkout develop			# 切换到分支develop
    git pull origin develop			# 远程develop拉下来和本地develop合并
    git checkout lujinkai			# 切换到分支lujinkai
    git merge develop --no-commit	# 将本地分支develop和当前分支lujinkai合并
  5. 开发代码

  6. 下班了,需要提交代码,但是在提交之前最好再执行一遍上一步,因为在你写代码的过程中,develop分支可能有其他人提交

    [root@dev ~]$git pull origin develop
  7. 添加文件到本地缓存区

    [root@dev ~]$git add .		# . 表示当前目录下的所有文件
  8. 提交内容到本地分支上

    git commit -m "注释, 提交说明"
  9. 上传本地分支到远程分支

    git push
    
    # 默认提交到本地分支对应的远程分支,或者可以显式指定
    git push origin lujinkai

之后每天 3 - 9 步骤走一遍

清除commit记录

  1. 克隆仓库 (这时工作目录里是master分支最后一次提交的内容)
  2. 创建一个新的空的分支
  3. 添加工作目录里所有文件到新的分支并做一次提交
  4. 删除master分支
  5. 将新的分支更名为master
  6. 强制更新到github仓库
git clone [URL] && cd [仓库名]           # 克隆git仓库,进入仓库
git checkout --orphan  new_branch		# 创建一个新的空的分支
git add -A								# 添加工作目录里所有文件到新的分支
git commit -am 'v1' 					# 做一次提交
git branch -D master					# 删除master分支
git branch -m master					# 将新的分支更名为master
git push origin master --force			# 强制更新到github仓库

DevOps

DevOps 是Development和Operations的组合,也就是开发和运维的简写

什么是持续集成(CI-Continuous integration)

持续集成是指多名开发者在开发不同功能代码的过程当中,可以频繁的将代码行合并到一起并切相互不影响工作

什么是持续部署(CD-continuous deployment)

是基于某种工具或平台实现代码自动化的构建、测试和部署到线上环境以实现交付高质量的产品,持续部署在某种程度上代表了一个开发团队的更新迭代速率

什么是持续交付(Continuous Delivery)

持续交付是在持续部署的基础之上,将产品交付到线上环境,因此持续交付是产品价值的一种交付,是产品价值的一种盈利的实现

GitLab

GitLab 和 GitHub一样属于第三方基于Git开发的作品,免费且开源,与Github类似,可以注册用户,任意提交你的代码,添加SSHKey等等。不同的是,GitLab是可以部署到自己的服务器上,简单来说可把 GitLab 看作个人版的GitHub

Git在每个用户都有一个完整的服务器,然后在有一个中央服务器,用户可以先将代码提交到本 地,没有网络也可以先提交到本地,然后在有网络的时候再提交到中央服务器,这样就大大方便了开发者,而相比CVS和SVN都是集中式的版本控制系统,工作的时候需要先从中央服务器获 取最新的代码,改完之后需要提交,如果是一个比较大的文件则需要足够快的网络才能快速提交完成,而使用分布式的版本控制系统,每个用户都是一个完整的版本库,即使没有中央服务器也可以提交代码或者回滚,最终再把改好的代码提交至中央服务器进行合并即可

SVN

每次提交的文件都单独保存, 即按照文件的提交时间区分不同的版本, 保存至不同的逻辑存储区域,后期恢复时候直接基于之前版本恢复。


Git

Gitlab 与 SVN的数据保存方式不一样,gitlab 虽然 也会在内部 对数据进行逻辑划分保存,但是当后期提交的数据如和之前交的数据没有变化,其就直接 快照之前的文件 ,而不是再将文件重新上传一份再保存一遍,这样既节省 了空间又加快了代码提交速度 。

git 缓存区与工作区等概念

  • 工作区 :clone 的代码或者开发自己编写代码文件所在 的目录 ,通常是代码所在的一 个服务的目录名称
  • 暂存区 :用于存储在工作区中对代码进行修改后的文件所保存的地方,使用 git add 添加
  • 本地仓库: 用于提交存储在工作区和暂存区中改过的文件地方,使 git commit 提交
  • 远程仓库 :多个开发人员共同协作提交代码的仓库,即 gitlab 服务器

Gitlab部署与使用

测试环境:内存4G以上
生产环境:建议CPU2C,内存8G,磁盘10G以上配置,和用户数有关

清华源 centos7:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/
清华源 ubuntu18.04:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/bionic/main/g/gitlab-ce/

安装

[root@gitlab src]$dpkg -i gitlab-ce_13.8.4-ce.0_amd64.deb	# 得等一会
...
configuration in /etc/gitlab/gitlab.rb file.	# 配置文件
...

配置

# 需要配置gitlab服务器地址和邮件地址
[root@gitlab src]$vim /etc/gitlab/gitlab.rb
external_url 'http://gitlab.ljk.local'		# 修改此行
# 增加下面行,可选邮件通知设置
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.qq.com"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = "441757636@qq.com"
gitlab_rails['smtp_password'] = "jwopcvnpcawdcbbg"
gitlab_rails['smtp_domain'] = "qq.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_tls'] = true
gitlab_rails['gitlab_email_from'] = "441757636@qq.com"

gitlab 相关目录:

/etc/gitlab     	#配置文件目录
/run/gitlab     	#运行pid目录
/opt/gitlab     	#安装目录
/var/opt/gitlab 	#数据目录
/var/log/gitlab 	#日志目录

初始化服务

修改完配置文件要执行此操作

[root@gitlab src]$gitlab-ctl reconfigure

常用命令:

gitlab-rails          #用于启动控制台进行特殊操作,如修改管理员密码、打开数据库控制台( gitlab-rails dbconsole)等
gitlab-psql           #数据库命令行
gitlab-rake           #数据备份恢复等数据操作

gitlab-ctl            #客户端命令行操作行
gitlab-ctl stop       #停止gitlab
gitlab-ctl start      #启动gitlab
gitlab-ctl restar     #重启gitlab
gitlab-ctl status     #查看组件运行状态
gitlab-ctl tail nginx #查看某个组件的日志

gitlab web 界面

username:root
注意,使用域名访问需要做hosts

关闭账号注册

默认情况下可以直接注册账号,一般都关闭此功能,由管理员统一注册用户

修改邮箱地址

新添加的邮箱不是默认的通知邮箱,下面是设置新邮箱为默认邮箱

设置完后,需要重新登录才能生效,然后可以把之前的默认邮箱删除

创建gitlab账户

创建成功后,需要查看邮件,在邮件链接中重置密码

创建组

使用管理员root创建组,一个组里面可以有多个项目分支,可以将开发添加到组里,再进行设置权限,不同的组对应公司不同的开发项目或者服务模块,不同的组中添加不同的开发人员帐号,即可实现对开发设置权限的管理

管理员创建项目

有三种方式:创建空项目、使用模板、导入项目

将用户添加到组

更多权限:https://docs.gitlab.com/ee/user/permissions.html

gitlab仓库developer权限无法push

默认develop权限无法merge和push到master分支的,可以修改:

在这里,也可以设置也可以保护其他分支

在项目中新建测试页面

使用 陆东生 用户登录,新建分支,继承自master:

git客户端测试clone项目

  1. ssh认证

    ssh-keygen -t rsa
    cat ~/.ssh/id_rsa.pub	# 将公钥复制到给gitlab
  2. clone

    git clone git@gitlab.ljk.local:testgroup/testproject.git

git 常用命令

运维常用:git pullgit clonegit reset

.gitignore

有些文件不需要上传,在 .gitignore 文件中设置忽略规则,示例:

# 项目根目录下创建.gitignore,规则如下:忽略.vscode目录和.gitignore文件
$ cat .gitignore	
/.vscode/
.gitignore

# 在src目录下创建.gitignore文件,规则如下:忽略全部,只保留.gitignore
# 目的是只保留src空目录
$ cat ./src/.gitignore	
*
!.gitignore

修改完 .gitignore 文件后,更新缓存:

git rm -r --cached .
git add .

gitlab数据备份恢复

  1. 备份前必须先停止gitlab两个服务

    [root@gitlab ~]$gitlab-ctl stop unicorn
    [root@gitlab ~]$gitlab-ctl stop sidekiq
  2. 备份数据

    [root@gitlab ~]$gitlab-rake gitlab:backup:create
    ...
    Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data 
    and are not included in this backup. You will need these files to restore a backup.
    Please back them up manually.
    
    # 以上warning表示gitlab.rb和gitlab-secrets.json两个文件包含敏感信息。未被备份到备份文件中。需要手动备份,这两个文件位于 /etc/gitlab
    
    [root@gitlab ~]$gitlab-ctl start	# 备份完成后启动gitlab

    备份数据位于 /var/opt/gitlab/backups/

    [root@gitlab ~]$ls /var/opt/gitlab/backups/
    1614392268_2021_02_27_13.8.4_gitlab_backup.tar
  3. 查看要恢复的文件

    /var/opt/gitlab/backups/   # Gitlab数据备份目录,需要使用命令备份的
    /var/opt/gitlab/nginx/conf # nginx配置文件
    /etc/gitlab/gitlab.rb      # gitlab配置文件
  4. 删除项目和用户信息

  5. 执行恢复

    # 恢复前先停止两个服务
    [root@gitlab ~]$gitlab-ctl stop unicorn
    [root@gitlab ~]$gitlab-ctl stop sidekiq
    # 恢复时指定备份文件的时间部分,不需要指定文件的全名
    [root@gitlab ~]$gitlab-rake gitlab:backup:restore BACKUP=备份文件名
  6. 恢复后再将之前停止的两个服务启动

    gitlab-ctl start

gitlab汉化

虽然不推荐,但是有需求,基于第三方开发爱好者实现

汉化包地址: https://gitlab.com/xhang/gitlab

git clone https://gitlab.com/xhang/gitlab.git
head -1 /opt/gitlab/version-manifest.txt	# 查看当前gitlab版本
cd gitlab
git diff v11.9.8 v11.9.8-zh
git diff v11.9.8 v11.9.8-zh >/root/v11.9.8-zh.diff
gitlab-ctl stop
patch -f -d /opt/gitlab/embedded/service/gitlab-rails -p1 </root/v11.9.8-zh.diff
gitlab-ctl reconfigure
gitlab-ctl start
gitlab-ctl status

常见的代码部署方式

蓝绿部署

不停老版本代码(不影响上一个版本访问),而是在另外一套环境部署新版本然后进行测试,测试通过后将用户流量切到新版本,其特点为业务无中断,升级风险相对较小

具体过程:

  1. 当前版本业务正常访问(V1)
  2. 在另外一套环境部署新代码(V2),代码可能是增加了功能或者是修复了某些bug
  3. 测试通过之后将用户请求流量切到新版本环境
  4. 观察一段时间,如有异常直接切换旧版本,没有异常就删除老版本
  5. 下次升级,将旧版本升级到新版本(V3)

金丝雀发布

金丝雀发布也叫灰度发布,是指在黑与白之间,能够平滑过渡的一种发布方式,灰度发布是增量发布的一种类型,灰度发布是在原有版本可用的情况下,同时部署一个新版本应用作为“金丝雀”(小白鼠),测试新版本的性能和表现,以保障整体系统稳定的情况下,尽早发现、调整问题。因此,灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度

具体过程:

  1. 准备好部署各个阶段的工件,包括:构建组件,测试脚本,配置文件和部署清单文件。
  2. 从负载均衡列表中移除掉“金丝雀”服务器。
  3. 升级“金丝雀”应用(排掉原有流量并进行部署)。
  4. 对应用进行自动化测试。
  5. 将“金丝雀”服务器重新添加到负载均衡列表中(连通性和健康检查)。
  6. 如果“金丝雀”在线使用测试成功,升级剩余的其他服务器。(否则就回滚)

滚动发布

滚动发布,一般是取出一个或者多个服务器停止服务,执行更新,并重新将其投入使用。周而复始,直到集群中所有的实例都更新成新版本,此方式可以防止同时升级,造成服务停止

A/B测试

A/B测试也是同时运行两个APP环境,但是和蓝绿部署完全是两码事,A/B 测试是用来测试应用功能表现的方法,例如可用性、受欢迎程度、可见性等等,蓝绿部署的目的是安全稳定地发布新版本应用,并在必要时回滚,即蓝绿部署是同一时间只有一套正式环境环境在线,而A/B测试是两套正式环境同时在线,一般用于多个产品竟争时使用