Deploy Jaeger in Kubernetes

Preparation

通过一张 Jaeger 的架构图,我们可以知道,要在我们的开发环境中部署一套Jaeger,需要部署以下几个组件

  1. jaeger-agent
  2. jaeger-collector
  3. data-storage
    1. Elasticsearch
    2. Cassandra

由于我们想将 jaeger 部署到 k8s 集群中,针对于这个特定的部署环境,我们可以对部署方案做如下的梳理:

通过对GitHub - jaegertracing/jaeger-kubernetes: Support for deploying Jaeger into Kubernetes的了解,我们可以知道,其实 jaeger 本身的组件部署时比较简单的,直接 kubectl applpy 一个编排文件即可搞定。唯一比较麻烦的是对底层存储的配置。针对这样的情况,我们决定将 jaeger 部署的顺序做如下安排:

  1. 准备一个 k8s 集群(笔者有一个一主一从的 k8s 集群,基于虚拟机建立的),主从节点各挂在一块500G 的数据盘
  2. Ceph RBD 集群和 k8s 混布(╮(╯▽╰)╭,没办法,穷啊),创建需要分配存储的测试 pod,查看 pvc 和 pv 的创建情况
  3. 部署 Elasticsearch
  4. 部署 Jaeger,测试集群内部是否能够成功访问 jaeger-query
  5. 部署 ingress+nginx-ingress-controller,测试集群外部访问情况

本文默认用户已经部署好了 k8s 集群并且挂载了数据盘,因为 k8s 的部署步骤也比较复杂,足以写另外一篇文章了。而且对于挂载磁盘的问题来说,用户所处平台的不同(云主机,物理机,本地的虚拟机)可能处理的方式也不太一样。这两部分在本文中就不做过多的描述了。

部署 Ceph RBD 集群

Ref: https://tonybai.com/2016/11/07/integrate-kubernetes-with-ceph-rbd/ (感谢作者)
部署 ceph 的部分,都是借鉴作者的经验,这里只是做一个记录,帮自己梳理部署的步骤。

一个 Ceph RBD 集群可以大致分为三个部分:

  • Deploy Node:部署节点,相当于 k8s 集群中的 Master 节点
  • OSD Node: 存储节点
  • MON Node: 监控节点

首先,我们需要安装 ceph 官方提供的部署工具: ceph-deploy。由于 k8s 集群所在 Node 的系统是 Ubuntu,所以这里使用 apt-get 来安装

1
2
3
4
5
6
export CEPH_DEPLOY_REPO_URL=http://mirrors.163.com/ceph/debian-jewel
export CEPH_DEPLOY_GPG_URL=http://mirrors.163.com/ceph/keys/release.asc
(使用国内的镜像,速度会快一点)
将 deb https://mirrors.163.com/ceph/debian-jewel/ xenial main 写入到 /etc/apt/sources.list 中
apt-get update	
apt-get install ceph-deploy

为了方便起见,我们给安装 ceph 创建一个具有 root 权限的专门的 user

1
2
3
4
5
6
7
8
以下命令在每个Node上都要执行:

useradd -d /home/cephd -m cephd
passwd cephd

添加sudo权限:
echo "cephd ALL = (root) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/cephd
sudo chmod 0440 /etc/sudoers.d/cephd

将不同 Node 上面的 ssh public key 相互拷贝到 ~/.ssh_authorized_keys 文件中,且在 ~/.ssh_config 中配置好使用 cephd 账户登录到其他机器上面的捷径。如:

1
2
3
4
5
6
Host xr-service-mesh-lab
   Hostname xr-service-mesh-lab
   User cephd
Host xr-service-mesh-lab1
   Hostname xr-service-mesh-lab1
   User cephd

如果你之前在当前集群安装过 ceph,可以运行如下命令清理:

1
2
3
ceph-deploy purge xr-service-mesh-lab xr-service-mesh-lab1
ceph-deploy forgetkeys
ceph-deploy purgedata xr-service-mesh-lab xr-service-mesh-lab1

在集群的任意节点中,选取一个作为 deploy node,以 cephd 账户登录。在该节点上执行

ceph-deploy new xr-service-mesh-lab(nodeName)

将该节点同时作为 monitor 节点。执行了该命令之后,我们可以发现,在当前目录下会自动生成一些文件,这些文件是在之后的部署过程中会用到的。

编辑 ceph.conf文件,在末尾添加几行内容:

1
2
3
osd pool default size = 2 (该参数可以根据自己搭建的 ceph 集群的 osd 节点的个数修改)
osd max object name len = 256
osd max object namespace len = 64

接下来,执行

ceph-deploy install xr-service-mesh-lab xr-service-mesh-lab1

命令,在两个节点上安装好部署 ceph 集群所需要的 binary。

在 mon-node 上(如果你没有切换过节点操作的话,就是当前的 node,也是 deploy-node),进行初始化操作

ceph-deploy mon create-initial

执行完毕后,会在当前目录下观察到很多以 .keyring 后缀结尾的文件。之后,可以运行

ps aux | grep ceph

命令,查看 ceph-mon 进程已经运行。

截止目前,对于这个 ceph 集群,我们还有 osd 节点没有部署。osd 节点的部署分为两部分:prepare, activate

Prepare

执行这一步之前,最好是在 Node 上都挂载好了专用的数据盘,否则,只能是创建一个目录,使用「系统盘」的空间。笔者在部署的时候,在两个节点的_mnt目录下分别挂在了两块500G 的 数据盘,并且在这两块盘上分别创建了两个目录: /mnt/osd0 /mnt/osd1

在 deploy-node 上执行如下命令:ceph-deploy osd prepare xr-service-mesh-lab:/mnt/osd0 xr-service-mesh-lab1:/mnt/osd1

Activate

在 deploy-node 上执行如下命令:ceph-deploy osd activate xr-service-mesh-lab:/mnt/osd0 xr-service-mesh-lab1:/mnt/osd1 若发现如如下报错信息:

1
[WARNIN] 2016-11-04 14:25:40.325075 7fd1aa73f800 -1  ** ERROR: error creating empty object store in /mnt/osd0: (13) Permission denied

进一步查看 /mnt/osd0目录权限信息:

1
drwxr-sr-x 2 root root 4096 Nov  4 14:25 osd0

可以发现该目录的 user 和 group 都是 root,且除了创建者之外其余的 user 是没有 w 权限的,这也就解释了上面,创建目录因权限问题失败的现象。 再次观察执行 activate 命令输出的日志,可以发现:

1
2
3
... ...
[xr-service-mesh-lab][WARNIN] got monmap epoch 1
[xr-service-mesh-lab][WARNIN] command: Running command: /usr/bin/timeout 300 ceph-osd --cluster ceph --mkfs --mkkey -i 0 --monmap /mnt/osd0/activate.monmap --osd-data /mnt/osd0 --osd-journal /mnt/osd0/journal --osd-uuid 6def4f7f-4f37-43a5-8699-5c6ab608c89c --keyring /var/mnt/keyring --setuser cephd --setgroup cephd

我们在 Node 上启动 ceph-osd 进程使用的都是名为 cephd 的 user 和 group。所以,要处理这个问题,可以手动对 Node 上对应的目录使用chown -R cephd:cephd <dir>命令,更改目录权限。修复完成后可以重新执行 activate 命令,正常情况下会顺利通过。

Ceph rbd Cluster State

ceph 集群 activate 成功之后,可以在 deploy-node 上使用两个命令来查看集群的健康情况:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ ceph osd tree

ID WEIGHT  TYPE NAME                     UP/DOWN REWEIGHT PRIMARY-AFFINITY
-1 0.49959 root default
-2 0.01909     host xr-service-mesh-lab
 0 0.01909         osd.0                      up  1.00000          1.00000
-3 0.48050     host xr-service-mesh-lab1
 1 0.48050         osd.1                      up  1.00000          1.00000

$ ceps -s

    cluster d38afe19-16bd-40f1-b1e4-4249471d656c
     health HEALTH_OK
     monmap e1: 1 mons at {xr-service-mesh-lab=172.20.6.29:6789/0}
            election epoch 3, quorum 0 xr-service-mesh-lab
     osdmap e10: 2 osds: 2 up, 2 in; 10 remapped pgs
            flags sortbitwise,require_jewel_osds
      pgmap v31: 64 pgs, 1 pools, 0 bytes data, 0 objects
            11934 MB used, 473 GB / 511 GB avail
                  54 active+clean
                  10 active

可以看到,两个 osd 节点目前都是正常的

Cooperate with kubernetes

在部署了 ceph 集群之后,要想在 kubernetes 中通过 ceph 使用集群中的存储资源,还需要一个「桥梁」,它就是 Storage Class。Storage Class 究竟是什么,以及我们要如何构造一个 Storage Class。可以看下另外一篇文章,对 Storage Class 有一个基本的了解:notion-of-pv-and-pvc

要想创建 ceph rbd 的 Storage Class,根据如下链接所给出的步骤操作即可:https://github.com/kubernetes-incubator/external-storage/tree/master/ceph/rbd

Create pod with pv on ceph

在按照上面的步骤创建好了 ceph rbd 集群以及对应的 Storage Class 资源对象之后,就可以通过创建一个使用了存储资源的 Pod 来测试我们的 ceph rbd 集群是否工作正常。

首先,我们创建一个 PVC,在 ceph rbd 集群上申请一定量的存储资源:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: claim1
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: rbd
  resources:
    requests:
      storage: 1Gi

其次,创建一个 Pod,并且通过挂载的方式,在容器内使用我们刚刚申请的存储资源:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: gcr.io/google_containers/busybox:1.24
    command:
    - "/bin/sh"
    args:
    - "-c"
    - "touch /mnt/SUCCESS && exit 0 || exit 1"
    volumeMounts:
    - name: pvc
      mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
  - name: pvc
    persistentVolumeClaim:
      claimName: claim1

最终,我们可以看到 PV 成功的被创建:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
root@xr-service-mesh-lab:~/external-storage/ceph/rbd# kubectl -n xuran get pv pvc-556fbe5a-c074-11e8-a174-fa163eebca40 -o yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  annotations:
    pv.kubernetes.io/provisioned-by: ceph.com/rbd
    rbdProvisionerIdentity: ceph.com/rbd
  creationTimestamp: 2018-09-25T03:37:38Z
  finalizers:
  - kubernetes.io/pv-protection
  name: pvc-556fbe5a-c074-11e8-a174-fa163eebca40
  resourceVersion: "2755796"
  selfLink: /api/v1/persistentvolumes/pvc-556fbe5a-c074-11e8-a174-fa163eebca40
  uid: 59406bdb-c074-11e8-a174-fa163eebca40
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 1Gi
  claimRef:
    apiVersion: v1
    kind: PersistentVolumeClaim
    name: claim1
    namespace: xuran
    resourceVersion: "2755787"
    uid: 556fbe5a-c074-11e8-a174-fa163eebca40
  persistentVolumeReclaimPolicy: Delete
  rbd:
    image: kubernetes-dynamic-pvc-59252351-c074-11e8-9c73-dab2986e8f3a
    keyring: /etc/ceph/keyring
    monitors:
    - 172.20.6.29:6789
    pool: kube
    secretRef:
      name: ceph-secret
      namespace: kube-system
    user: kube
  storageClassName: rbd
status:
  phase: Bound

部署 ElasticSearch

在部署 elasticsearch 的时候,我们将参考 elasticsearch Chart 中的编排文件:https://github.com/helm/charts/tree/master/incubator/elasticsearch。它将帮助我们搭建一个含有三个 master 节点,两个 client 节点 ,两个 data 节点的 elasticsearch 集群。

在部署之前,对于上述编排文件,唯一需要改正的就是 elasticsearch 依赖的 storageclass。对此,我们可以使用helm template命令,对 storageclass 的相关参数进行修改,并且最终导出到一个 yaml 文件中,方便我们后续部署.

首先,我们需要下载相应的 chart 包,然后执行如下命令:

1
helm template elasticsearch --name jaeger-es --set cluster.name=tracing-es --set master.persistence.storageClass=ceph --set data.persistence.storageClass=ceph > jaeger-es.yaml

注意:persistence.storageClass 的值一定要和我们之前部署 ceph 时创建的 StorageClass 的 meta.name 的值相同

然后,我们可以执行 kubectl create -f jaeger-es.yaml -n xxx命令来部署 elasticsearch。

部署 Jaeger

在部署 Jaeger 的时候,我们参考:GitHub - jaegertracing/jaeger-kubernetes: Support for deploying Jaeger into Kubernetes。由于我们已经部署好了 elasticsearch,所以链接中关于存储中间件的内容我们可以不用关心。

首先,我们需要创建一个 configMap:

1
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/production-elasticsearch/configmap.yml

然后,开始部署 jaeger 相关的 component:

1
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/jaeger-production-template.yml

Jaeger 部署成功之后,可以在相应的 ns 下发现一个名为 jaeger-query 的 service。

1
2
3
4
5
root@xr-service-mesh-lab:~/external-storage/ceph/rbd# kubectl -n istio-system get svc | grep jaeger
jaeger-collector                    ClusterIP      10.103.47.166    <none>        14267/TCP,14268/TCP,9411/TCP                                                                                              5d
jaeger-es-elasticsearch-client      ClusterIP      10.100.101.147   <none>        9200/TCP                                                                                                                  5d
jaeger-es-elasticsearch-discovery   ClusterIP      None             <none>        9300/TCP                                                                                                                  5d
jaeger-query                        LoadBalancer   10.105.139.8     <pending>     80:31624/TCP                                                                                                              5d

如果是在集群内部,我们是可以直接通过这个 service 的地址访问 jaeger 的查询页面的。但是由于我们现在想在集群外部访问,可选择的方案有如下几种:

  1. 修改这个 service 的 type 为 nodePort 并指定一个宿主机的端口。这样在集群外部就可以通过集群的外网 IP:宿主机端口/path 的形式访问 jaeger-query。请求的流量将从宿主机的端口转发至目的 Pod 对应的端口。
  2. 使用 ingress+ingress-controller 的形式。 ingress 写入转发规则,属于一个具有文本记录功能的资源对象。ingress-controller 会根据 ingress 中写入的规则,将请求的流量转发到目的 Pod 对应的端口上。

如果想了解更多关于 k8s 中部署的服务和「访问」相关的问题,可以看下这个链接:https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/

部署 ingress+nginx-ingress-controller

首先,我们需要创建一个 Ingress:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: tracing
  namespace: istio-system
spec:
  rules:
  - host: tracing.istio.io
    http:
      paths:
      - path: /
        backend:
          serviceName: jaeger-query
          servicePort: 80

规则写的很清楚,当我们进行一次 host 为 tracing.istio.io,且 path 为根目录,端口为80的 http 请求的时候,流量就会被转发到同 ns 下的一个名为 jaeger-query 的 svc 上,最终,由这个 svc 将请求流量打向目的 Pod。

有了 ingress 还不行,还需要一个 ingress-controller 加载上面的规则并且根据它转发请求流量:

1
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml

在本次部署中,笔者使用了 nginx-ingress-controller。本质上来说,nginx-controller 就是对 nginx 的一个封装。你可以这样理解 ingress 和 nginx-ingress-controller 之间的关系:ingress 写入一系列的路由规则,nginx-ingress-controller 中封装的 nginx 会加载这些规则,以实现对指定请求的流量控制。

访问 jaeger-query

此时,将你的 k8s 集群的公网 ip 和 tracing.istio.io 的 DNS解析映射关系写入你所在机器的 /etc/hosts 文件,然后就可以在浏览器通过访问 http://tracing.istio.io 看到 jaeger-query 的 UI 界面