前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kubernetes集群中Java应用的Java Agent自动注入方式分享

Kubernetes集群中Java应用的Java Agent自动注入方式分享

作者头像
HelloMin
发布2022-06-27 11:40:06
1.2K0
发布2022-06-27 11:40:06
举报
文章被收录于专栏:Pair ProgrammingPair Programming

导言:

最近在试用一个付费软件,主要是希望使用他们的Java Agent配合我们的Java应用采集一些数据,给应用做一些分析。试用前对方说的天花乱坠,什么只要一个命令,K8S集群上的对应应用就会自动带着他们的Java Agent跑起来,完全不用改任何应用代码,听的我也很有兴趣看看效果到底如何。当然,实际过程坎坷多了,毕竟,销售的嘴骗人的鬼...不过在不断地和对方研发一起debug的过程中,渐渐的也猜出了所谓的“自动”到底是怎么做到的,写一篇文章和大家分享,也许一样的思路,也能用到今后的工作中,解决一些问题。

首先展示一下所谓的一个命令的效果吧。如果“一切顺利”,确实也只需几行命令就能解决问题。

首先需要创建一个operator,里面定义了一个叫HellominAgent的自定义资源。然后创建一个HellominAgent,里面已经配置了一些例如应用的Statefulset名字等必要的信息。然后,两个kubectl解决问题:

代码语言:javascript
复制
$ kubectl create -f hellomin-operator.yaml
$ kubectl create -f hellomin-agent.yaml

此时再查看应用所在Pod的情况,会发现所有的Pod开始进入Terminating状态,等他们再次启动处于Running状态,我们登录进container,ps看看进程,会发现,咦,我们的Tomcat进程已经带着一些Jave Agent参数跑起来了:

代码语言:javascript
复制
# 进入tomcat Pod
$ kubectl exec -it tomcat bash
# 查看tomcat进程信息
$ ps -ef | grep tomcat
# 动态植入之前的tomcat启动命令,省略非必要信息
java  org.apache.catalina.startup.Bootstrap start
# 动态植入之后的tomcat启动命令,省略非必要信息
java  -Dhellomin=amazing -javaagent:/opt/hellomin/hellomin-agent.jar org.apache.catalina.startup.Bootstrap start

所以这里有几个问题:

  • 这些参数是怎么传进去作为Tomcat启动参数的?
  • 这里的Agent Jar包是从哪里来的?

让我们来一一解答。

从天而降的Jar包?InitContainer早已准备好了一切

首先,要给Tomcat的启动参数添加Java Agent,我们首先需要有Agent对应的Jar包吧?为啥重启了Pod之后,Jar包就凭空出现了呢?答案就在重启之后的Pod信息中。

通过查看被植入之后的Statefulset信息,我们可以看到类似这样的一些语句:

代码语言:javascript
复制
# 以yaml文件格式查看此时Pod信息
$ kubectl get sts hello-min -o yaml
# 此前的输出省略
initContainers:
- command:
   - /bin/sh
   - -c
   - 'cp -r /opt/hellomin/. /opt/hellomin-java'
  image: docker.io/hellomin/java-agent:latest
  name: hellomin-agent
# 此后省略

可以看到,initContainer字段多了一个container的信息,从执行的命令基本可以看出,这个container做的事情,就是从一个特定的image里面把agent的Jar包拷贝到了另一个目录下面,那这个目录是哪里来的,拿来做什么呢?

首先,查看这个目录对应的volume相关的信息。还是一样的方式查看Statefulset,发现下面多声明了一个volume信息:

代码语言:javascript
复制
volumes:
# 省略大量其他volume声明
- emptyDir: {}
  name: hellomin-agent-repo-java

这个Volume没有挂载出去,只是整个Pod共享的一块空间。

然后,我们在应用的container信息下面的volume信息里,也发现了这个volume,它被挂载到了我们应用的container里,即我们的应用是可以访问这个共享的Volume的存储空间的。注意,此处挂载的路径,正是之前initContainer把Jar包搬过去的地方哦。

代码语言:javascript
复制
volumeMounts:
  # 省略其他volume信息
  - mountPath: /opt/hellomin-java
    name: hellomin-agent-repo-java

然后,之前的initiContainer也可以访问这个volume,因为它也同样挂到了这个initContainer下面:

代码语言:javascript
复制
# 以yaml文件格式查看此时Pod信息
$  kubectl get sts shello-min -o yaml
# 此前省略
initContainers:
   - command:
# 中间忽略
   volumeMounts:
      - mountPath: /opt/hellomin-java
        name: hellomin-agent-repo-java
# 此后省略

于是发生了什么基本就可以猜到了: Agent的Jar包被放在java-agent这个image的/opt/hellomin目录下,而这个initContainer要做的事情,就是把这个image里面的Jar包从这个目录拷贝到了一个我们的应用和Agent的image都能访问到的目录下。于是,我们的应用就可以访问这个Agent的Jar包了,从我们的角度来看,这个Jar包就“凭空出现了”~

能做到这一点,和initContainer的工作方式也有很大关系。在Statefulset中的Container运行之前,所有initContainer会依次运行并结束退出,所以,他们非常适合用来做一些应用运行之前的准备工作,这里的用法就是一个很好的例子。

有了Jar包,下面我们来想办法把它加到启动参数里面吧!

谁给Tomcat添加了参数?环境变量解决问题

Jar包的来源找到了,下一步就是把这个Jar包的启动信息添加到Tomcat的启动参数里面去了,这怎么能不修改任何代码就做到呢?

再次查看修改后的Statefulset信息,我们可以发现这样几行之前没有的信息:

代码语言:javascript
复制
spec:  
  containers:    
    - env:    
       - name: JAVA_OPTS      
         value: ' -Dhellomin=amazing -javaagent:/opt/hellomin-java/agent.jar'

Emmm,这不就是之前神秘出现的启动参数嘛?所以,所谓的不用修改代码,其实就是通过修改了Container的环境变量,把所需要添加的参数都通过环境变量的方式传递给Pod,这样在Container里,我们就可以读到这些参数了。

仅仅能读到还不够,是谁帮我们把这些参数加到启动参数里去的呢?这里其实Tomcat也是帮了一点小忙滴~

Tomcat的默认启动脚本Catalina.sh里面有这么一段:

代码语言:javascript
复制
eval $_NOHUP "\"$_RUNJAVA\"" "$JAVA_OPTS" "$CATALINA_OPTS" \
# 省略不必要的参数
org.apache.catalina.startup.Bootstrap "$@" start

注意到中间的$JAVA_OPTS了吗,就是它啦!Tomcat启动的时候默认是带着这个环境变量的,所以你只要给这个环境变量里塞了东西,启动的时候就会被添加到启动参数里面了哟。

谁给它修改一切的权力?K8S中的权限管理

所以,谁给了这个HellominAgent这么大的权力,在我们的应用所在的container里面加了这么些东西?答案就在一开始运行的两行kubectl命令里。

说到这里,我们不得不聊聊K8S里面的权限管理问题。仔细查看hellomin-operator.yaml文件,也就是agent的operator文件,我们会发现这么几行配置:

首先,我们创建了一个叫做hellomin-cluster-agent的用户。

代码语言:javascript
复制
apiVersion: v1 
kind: ServiceAccount
metadata:
   name: hellomin-cluster-agent
   namespace: hellomin

然后,我们创建了一个叫做hellomin-cluster-agent-instrumentation的ClusterRole,也就是集群中的权限角色,在这里,我们定义了这个ClusterRole可以在这个集群中做哪些操作,可以看到对Statefulset和pod,这个角色的用户都是拥有update权限的,可以进行更改的。

代码语言:javascript
复制
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
   name: hellomin-cluster-agent-instrumentation
rules:
  - apiGroups:
      - ""
    resources:
      - pods
      - pods/exec
      - secrets
      - configmaps
    verbs:
      - create
      - update
      - delete
  - apiGroups:
      - apps
    resources:
      - daemonsets
      - statefulsets
      - deployments
      - replicasets
    verbs:
      - update

最后,我们创建了一个叫做hellomin-cluster-agent-instrumentation的ClusterRoleBinding,把刚才定义的Account和Role建立了联结,即赋予了hellomin用户这个Role所拥有的权限。

代码语言:javascript
复制
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: hellomin-cluster-agent-instrumentation
subjects:
  - kind: ServiceAccount
    name: hellomin-cluster-agent
    namespace: hellomin
roleRef:
  kind: ClusterRole
  name: hellomin-cluster-agent-instrumentation
  apiGroup: rbac.authorization.k8s.io

然后,在我们自己定义的HellominAgent资源的配置里,我们为它配置了这个已经赋好权限的账户:

代码语言:javascript
复制
kind: HellominAgent 
metadata:
  namespace: hellomin
spec:
  serviceAccountName: hellomin-cluster-agent
  # 省略其他配置

因此,通过我们自定义的这个HellominAgent创建出来的pod,就可以对集群中的Statefulset进行修改了。

最后,一切看上去都特别好,为啥我说坎坷呢?答案很简单。如果我们的应用就是直接使用Tomcat的脚本启动,确实挺好,但我们并没有...于是,我们根本就没有用到那个被赋值的环境变量,于是什么也没发生...

所以咯,遇到具体业务,还是要具体问题具体分析,不过这套思路还是很有趣的,一眼看去还是有种黑科技的感觉的,只是拆开来说白了,就发现其实也都是常见的打法吧?

下次再见!

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 从天而降的Jar包?InitContainer早已准备好了一切
  • 谁给Tomcat添加了参数?环境变量解决问题
  • 谁给它修改一切的权力?K8S中的权限管理
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档