0%

介绍

Loki是 Grafana Labs 团队最新的开源项目,是一个水平可扩展,高可用性,多租户的日志聚合系统。它的设计非常经济高效且易于操作,因为它不会为日志内容编制索引,而是为每个日志流编制一组标签。项目受 Prometheus 启发,官方的介绍就是:Like Prometheus, but for logs.,类似于 Prometheus 的日志系统。

与其他日志聚合系统相比,Loki具有下面的一些特性:

  • 不对日志进行全文索引。通过存储压缩非结构化日志和仅索引元数据,Loki 操作起来会更简单,更省成本。
  • 通过使用与 Prometheus 相同的标签记录流对日志进行索引和分组,这使得日志的扩展和操作效率更高。
  • 特别适合储存 Kubernetes Pod 日志; 诸如 Pod 标签之类的元数据会被自动删除和编入索引。
  • 受 Grafana 原生支持。

Loki 由以下3个部分组成:
Loki

  1. loki是主服务器,负责存储日志和处理查询。
  2. promtail是代理,负责收集日志并将其发送给 loki 。
  3. Grafana用于 UI 展示。

Loki安装

参考官网

添加/更新helm

helm repo add loki https://grafana.github.io/loki/charts
helm repo update

安装loki(使用默认配置)

helm upgrade --install loki loki/loki-stack

安装loki(设置namespace)

helm upgrade --install loki --namespace=loki loki/loki

安装loki(更改一些配置)

helm upgrade --install loki loki/loki-stack  --set grafana.enabled=true,prometheus.enabled=true,prometheus.alertmanager.persistentVolume.enabled=false,prometheus.server.persistentVolume.enabled=false

这边loki使用的镜像是grafana/loki:v1.2.0,建议提前拉取下来。

docker pull grafana/loki:v1.2.0

其它一些信息,可以参考官网配置。

Promtail安装

参考官网

helm upgrade --install promtail loki/promtail --set "loki.serviceName=loki" --namespace=loki

这边loki使用的镜像是grafana/promtail:v1.2.0,建议提前拉取下来。

docker pull grafana/promtail:v1.2.0

其它一些信息,可以参考官网配置。

集成Grafana,实现页面查询日志

前文使用Helm一键安装Prometheus Operator已经安装了grafana服务,我们可以直接使用。

登录grafana,选择添加数据源

添加Loki数据源

数据源列表中选择Loki,配置服务地址

如果grafanaloki在同一个namespace,只需写服务名即可。
如果是在不同的namespace,那么要写完整DNS地址。

添加Loki数据源

切换到grafana左侧区域的Explore,进入loki页面

添加Loki数据源

点击Log labels就可以把当前系统采集的日志标签给显示出来,可以根据这些标签进行日志的过滤查询

添加Loki数据源

常见问题

...........
level=error ts=2019-12-17T05:43:00.189385282Z caller=filetarget.go:272 msg="failed to tail file, stat failed" error="stat /var/log/pods/kube-system_kube-flannel-ds-amd64-btn7m_5088625c-bba8-41d6-86c8-dc738d3b43ab/kube-flannel/3.log: no such file or directory" filename=/var/log/pods/kube-system_kube-flannel-ds-amd64-btn7m_5088625c-bba8-41d6-86c8-dc738d3b43ab/kube-flannel/3.log
level=error ts=2019-12-17T05:43:00.192476724Z caller=filetarget.go:272 msg="failed to tail file, stat failed" error="stat /var/log/pods/kube-system_kube-flannel-ds-amd64-btn7m_5088625c-bba8-41d6-86c8-dc738d3b43ab/install-cni/5.log: no such file or directory" filename=/var/log/pods/kube-system_kube-flannel-ds-amd64-btn7m_5088625c-bba8-41d6-86c8-dc738d3b43ab/install-cni/5.log
......

提示找不到/var/log/pods目录下的日志文件,无法tail

首先我们可以进入promtail容器内,到该目录下查看下是否有该文件,通过cat命令看看是否有日志。

默认安装promtail,它会将主机/var/log/pods/var/lib/docker/containers目录通过volumes方式挂载到promtail容器内。如果安装dockerk8s都是采用默认配置,应该不会存在读不到日志的问题。

{
    "name": "docker",
    "hostPath": {
        "path": "/var/lib/docker/containers",
        "type": ""
    }
},
    {
    "name": "pods",
    "hostPath": {
        "path": "/var/log/pods",
        "type": ""
    }
}

本人因为主机系统盘太小,设置了docker镜像的目录到挂载磁盘/data目录下,所以需要修改默认volumes配置。

{
    "name": "docker",
    "hostPath": {
        "path": "/data/docker/containers",
        "type": ""
    }
},
    {
    "name": "pods",
    "hostPath": {
        "path": "/var/log/pods",
        "type": ""
    }
}
//注意对应的volumeMounts也要修改
"volumeMounts": [
              {
                "name": "docker",
                "readOnly": true,
                "mountPath": "/data/docker/containers"
              },
              {
                "name": "pods",
                "readOnly": true,
                "mountPath": "/var/log/pods"
              }
            ]

上面volumesvolumeMounts都要修改,因为/var/log/pods目录下的日志文件其实是个软链接,指向的是docker/containers目录下的日志文件。如果只修改了volumes,那么promtail容器内可以找到日志文件,但是打开确实空的,因为它只是个软连接。

[root@node1 log]# ll /var/log/pods/monitoring_promtail-bs5cs_5bc5bc90-bac9-480d-b291-4caadeff2236/promtail/
total 4
lrwxrwxrwx 1 root root 162 Dec 17 14:04 0.log -> /data/docker/containers/db45d5118e9508817e1a2efa3c9da68cfe969a2b0a3ed42619ff61a29cc64e5f/db45d5118e9508817e1a2efa3c9da68cfe969a2b0a3ed42619ff61a29cc64e5f-json.log

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
  labels:
    app: nfs-client-provisioner
    chart: nfs-client-provisioner-1.2.8
    release: test-storageclass
  name: nfs-client
parameters:
  archiveOnDelete: "true"
provisioner: cluster.local/test-storageclass-nfs-client-provisioner

注意事项

  • provisioner:这个是安装nfs-client-provisioner是定义的provisionerName,如果配置中没有指明,会自动生成。
  • 添加storageclass.kubernetes.io/is-default-class: "true"表面这是一个默认的StorageClass 资源对象
  • name:它处使用这个StorageClass时要指定的名称。

K8S启动的POD,默认时区与中国相差8小时,在排查问题或者查看日志时非常不方便。

增加容器环境变量,指定时区

docker run -it --rm -e "TZ=Asia/Shanghai" centos

对应k8s中配置:

apiVersion: v1
kind: Pod
metadata:
  name: pod-env-tz
spec:
  containers:
  - name: ngx
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    env:
      - name: TZ
        value: Asia/Shanghai

容器启动时,挂载到宿主机时区文件

docker run -it --rm -v /etc/localtime:/etc/localtime  centos

对应k8s中配置

apiVersion: v1
kind: Pod
metadata:
  name: pod-vol-tz
spec:
  containers:
  - name: ngx
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: tz-config
      mountPath: /etc/localtime
      readOnly: true
  volumes:
  - name: tz-config
    hostPath:
      path: /etc/localtime

以上两种方法都可以解决问题,但是缺点明显,每次新建POD都需要指定。

推荐使用PodPreset方式

PodPreset(参考中文文档

PodPreset用来给指定标签的Pod注入额外的信息,如环境变量、存储卷等。这样,Pod模板就不需要为每个Pod都显式设置重复的信息。

开启PodPreset

  • 开启API settings.k8s.io/v1alpha1/podpreset
  • 开启准入控制 PodPreset

默认情况下PodPreset不可用

[root@k8s-master ~]# kubectl get podpreset
error: the server doesn't have a resource type "podpreset"

开启之后应该是

[root@k8s-master ~]# kubectl get podpreset
No resources found.

如何开启PodPreset?

编辑/etc/kubernetes/manifests/kube-apiserver.yaml

spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=192.168.1.210
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction,PodPreset
    - --enable-bootstrap-token-auth=true
    - --runtime-config=settings.k8s.io/v1alpha1=true
    - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt

添加一行配置- --runtime-config=settings.k8s.io/v1alpha1=true,enable-admission-plugins后面增加PodPreset

等待ApiServer自动重启完成。

创建PodPreset

apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
  name: allow-tz-env
  namespace: default
spec:
  selector:
    matchLabels:
  env:
    - name: TZ
      value: Asia/Shanghai
[root@k8s-master ~]# kubectl get podpresets
NAME           CREATED AT
allow-tz-env   2019-12-16T07:57:54Z

现在创建一个新的POD,查看其环境变量。

测试

我这边简单测试,删除一个已有的consul容器,等它重启之后,查看它的环境变量
consul

可以看到,容器最后环境变量确实增加的时区配置。

busybox通过时区设置时间不生效问题

原因参考这篇

解决办法,通过挂载磁盘/etc/localtime,也可以通过PodPreset方式,为每个创建的POD自动添加挂载

apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
  name: allow-tz-env
spec:
  selector:
    matchLabels:
  volumeMounts:
    - mountPath: /etc/localtime
      name: tz-config
      readOnly: true
  volumes:
    - name: tz-config
      hostPath: 
        path: /etc/localtime

这样新增的POD会自动挂载宿主机上的时间文件。

kubectl describe 命令查看

Events:
  Type     Reason     Age                   From               Message
  ----     ------     ----                  ----               -------
  Normal   Scheduled  13m                   default-scheduler  Successfully assigned kube-system/kube-flannel-ds-amd64-vfhnj to node-d1
  Normal   Pulled     13m                   kubelet, node-d1   Container image "quay.io/coreos/flannel:v0.11.0-amd64" already present on machine
  Normal   Created    13m                   kubelet, node-d1   Created container install-cni
  Normal   Started    13m                   kubelet, node-d1   Started container install-cni
  Normal   Pulled     10m (x5 over 13m)     kubelet, node-d1   Container image "quay.io/coreos/flannel:v0.11.0-amd64" already present on machine
  Normal   Created    10m (x5 over 13m)     kubelet, node-d1   Created container kube-flannel
  Normal   Started    10m (x5 over 13m)     kubelet, node-d1   Started container kube-flannel
  Warning  BackOff    3m39s (x30 over 12m)  kubelet, node-d1   Back-off restarting failed container

kubectl logs命令查看

[root@master ~]# kubectl logs  kube-flannel-ds-amd64-9w5nq -n kube-system
I1216 05:59:40.055608       1 main.go:527] Using interface with name eth0 and address 192.168.1.82
I1216 05:59:40.055666       1 main.go:544] Defaulting external address to interface address (192.168.1.82)
E1216 06:00:10.056546       1 main.go:241] Failed to create SubnetManager: error retrieving pod spec for 'kube-system/kube-flannel-ds-amd64-9w5nq': Get https://10.96.0.1:443/api/v1/namespaces/kube-system/pods/kube-flannel-ds-amd64-9w5nq: dial tcp 10.96.0.1:443: i/o timeout

问题排查
网络问题?
通过curl命令测试,网络没有问题。

测试kube-proxy

重启该节点上的kube-proxy容器,并查看日志

kubectl delete pod kube-proxy-vtd27 -n kube-system
[root@master ~]# kubectl logs kube-proxy-pljct -n kube-system
W1216 06:30:51.741835       1 proxier.go:513] Failed to load kernel module ip_vs_wrr with modprobe. You can ignore this message when kube-proxy is running inside container without mounting /lib/modules
W1216 06:30:51.742536       1 proxier.go:513] Failed to load kernel module ip_vs_sh with modprobe. You can ignore this message when kube-proxy is running inside container without mounting /lib/modules
W1216 06:30:51.748495       1 proxier.go:513] Failed to load kernel module ip_vs_wrr with modprobe. You can ignore this message when kube-proxy is running inside container without mounting /lib/modules
W1216 06:30:51.749223       1 proxier.go:513] Failed to load kernel module ip_vs_sh with modprobe. You can ignore this message when kube-proxy is running inside container without mounting /lib/modules
E1216 06:30:51.750805       1 server_others.go:259] can't determine whether to use ipvs proxy, error: IPVS proxier will not be used because the following required kernel modules are not loaded: [ip_vs_wrr ip_vs_sh]
I1216 06:30:51.757054       1 server_others.go:143] Using iptables Proxier.
W1216 06:30:51.757122       1 proxier.go:321] clusterCIDR not specified, unable to distinguish between internal and external traffic
I1216 06:30:51.757338       1 server.go:534] Version: v1.15.0

问题

这里明显有个问题。一些需要的内核模块加载失败,参考安装文档,已经配置过内核模块。重新尝试,问题依然存在。

[root@master ~]# cat > /etc/sysconfig/modules/ipvs.modules < #!/bin/bash
> modprobe -- ip_vs
> modprobe -- ip_vs_rr
> modprobe -- ip_vs_wrr
> modprobe -- ip_vs_sh
> modprobe -- nf_conntrack_ipv4
> EOF
[root@master ~]# chmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep -e ip_vs -e nf_conntrack_ipv4
ip_vs_sh               12688  0 
ip_vs_wrr              12697  0 
ip_vs_rr               12600  150 
ip_vs                 145497  156 ip_vs_rr,ip_vs_sh,ip_vs_wrr
nf_conntrack_ipv4      15053  6 
nf_defrag_ipv4         12729  1 nf_conntrack_ipv4
nf_conntrack          139224  9 ip_vs,nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,nf_nat_masquerade_ipv4,nf_conntrack_netlink,nf_conntrack_ipv4,nf_conntrack_ipv6
libcrc32c              12644  3 ip_vs,nf_nat,nf_conntrack
[root@master ~]# lsmod | grep -e ip_vs -e nf_conntrack_ipv4
ip_vs_sh               12688  0 
ip_vs_wrr              12697  0 
ip_vs_rr               12600  150 
ip_vs                 145497  156 ip_vs_rr,ip_vs_sh,ip_vs_wrr
nf_conntrack_ipv4      15053  6 
nf_defrag_ipv4         12729  1 nf_conntrack_ipv4
nf_conntrack          139224  9 ip_vs,nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,nf_nat_masquerade_ipv4,nf_conntrack_netlink,nf_conntrack_ipv4,nf_conntrack_ipv6
libcrc32c              12644  3 ip_vs,nf_nat,nf_conntrack

问题解决

由于ipvs已经加入到内核主干,所以需要内核模块支持,请确保内核已经加载了相应模块;如不确定,执行以下脚本,以确保内核加载相应模块,否则会出现failed to load kernel modules: [ip_vs_rr ip_vs_sh ip_vs_wrr]错误

cat > /etc/sysconfig/modules/ipvs.modules < /dev/null 2>&1
    if [ $? -eq 0 ]; then
        /sbin/modprobe \${kernel_module}
    fi
done
EOF
chmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep ip_vs

执行后应该如下图所示,如果lsmod | grep ip_vs并未出现 ip_vs_rr 等模块;

[root@node-d1 ~]# lsmod | grep ip_vs
ip_vs_ftp              13079  0 
ip_vs_sed              12519  0 
ip_vs_nq               12516  0 
ip_vs_sh               12688  0 
ip_vs_dh               12688  0 
ip_vs_lblcr            12922  0 
ip_vs_lblc             12819  0 
ip_vs_wrr              12697  0 
ip_vs_wlc              12519  0 
ip_vs_lc               12516  0 
nf_nat                 26583  5 ip_vs_ftp,nf_nat_ipv4,nf_nat_ipv6,xt_nat,nf_nat_masquerade_ipv4
ip_vs_rr               12600  136 
ip_vs                 145497  162 ip_vs_dh,ip_vs_lc,ip_vs_nq,ip_vs_rr,ip_vs_sh,ip_vs_ftp,ip_vs_sed,ip_vs_wlc,ip_vs_wrr,ip_vs_lblcr,ip_vs_lblc
nf_conntrack          139224  9 ip_vs,nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,nf_nat_masquerade_ipv4,nf_conntrack_netlink,nf_conntrack_ipv4,nf_conntrack_ipv6
libcrc32c              12644  3 ip_vs,nf_nat,nf_conntrack

最后重启Kube-proxyflannel容器,问题解决。

a标签href不跳转 禁止跳转

当页面中a标签不需要任何跳转时,从原理上来讲,可分如下两种方法:

标签属性href,使其指向空或不返回任何内容。如:

<a href="javascript:void(0);" >点此无反应javascript:void(0)</a>
<a href="javascript:;" >点此无反应javascript:</a>

标签事件onclick,阻止其默认行为。如:

<a href="" οnclick="return false;">return false;</a>
<a href="#" οnclick="return false;">return false;</a>

注意:只有一个href="#"是不可以的。

用户表

user_id user_name
1 zhangsan
2 lisi
3 wangwu
4 zhaoliu

另一张money表,表示了借钱的关系

id from to how
1 1 2 100
2 3 4 100

关联查询

select m.id,u1.user_name,u2.user_name,m.how
from money as m
left outer join user u1 on u2.user_id=m.from
left outer join user u3 on u3.user_id=m.to

显示的结果

id user_name user_name how
1 zhangsan lisi 100
2 wangwu zhaoliu 100

问题

这里有个小问题,中间两列显示的名称一样,都是user_name,这样就不好辨别了,在Mybatis中如果想用

@ResultType(实体类名称.class)

来定义返回的类型,因为两个字段名一样,定义的实体类肯定不能一样,就会导致返回没有结果,怎么处理呢?

关联查询时,指定结果字段别名

select m.id,u1.user_name as user_name1,u2.user_name as user_name2,m.how
from money as m
left outer join user u1 on u2.user_id=m.from
left outer join user u3 on u3.user_id=m.to