java中容器是什么
优美不仅是令人钦佩的人类素质:它还是任何应用程序的必备条件,尤其是当它承担着关键任务领域的负担时。
UltraESB在保持运行正常(包括关闭)的过程中一直保持良好的历史。 新的UltraESB-X遵循了这一传统,并在其17.07版本中实现了正常关机 。
当我们为集成平台(IPS)制作 ips-worker Docker映像作为UltraESB-X的定制版本时,我们可以保证在平台上运行的ESB可以正常关闭–或我们认为如此。
不幸的是没有。
一旦我们重新部署或更改集群的复制计数 ,该集群下运行的所有ESB实例都将终止(并生成新实例来代替它们)。 终止应该是优雅的; ESB将首先停止接受任何新的传入消息,并推迟内部关闭序列几秒钟,直到运行中的消息处理完成或超时结束释放为止。
在基于Kubernetes的主流IPS版本中,我们通过K8s API以及数据库附加程序检索ESB实例(荚)的日志,以便以后进行分析。 在分析日志时,我们注意到,无论日志存储量有多大,我们都从未见过任何ESB关闭日志。 好像ESB在收到终止信号后就被残酷杀害。
为了研究这个问题,我从一个简化的Java程序开始:注册了一个关闭钩子(一种在Java中实现优雅关闭的世界著名方式,我们在两个ESB中都使用了该钩子) ,并且该方法一直运行着,打印一些文本定期(指示main线程处于活动状态)。 触发关闭挂钩后,我立即中断main线程,更改输出以指示我们正在关闭,然后让处理程序在几秒钟后完成(与“模拟”正常关闭相一致)。
class Kill {
private static Thread main;
public static void main(String[] a) throws Exception {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
System.out.println("TERM");
main.interrupt();
for (int i = 0; i < 4; i++) {
System.out.println("busy");
try {
Thread.sleep(1000);
} catch (Exception e) {}
}
System.out.println("exit");
}
}));
main = Thread.currentThread();
while (true) {
Thread.sleep(1000);
System.out.println("run");
}
}
}
测试它非常简单:
javac Kill.java
java Kill
当程序继续打印时:
run
run
run
...
按Ctrl + C查看会发生什么:
...
run
run
^CTERM
busy
Exception in thread "main" java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Kill.main(Kill.java:22)
busy
busy
busy
exit
看起来挺好的。
完成后,将其转换为成熟的Docker容器仅花费了几分钟,并生成了以下Dockerfile :
FROM openjdk:8-jre-alpine
ADD Kill*.class /
ENTRYPOINT ["java", "Kill"]
docker build -t kill:v1 .
接下来,我用新图像运行了一个容器:
docker run -it --rm kill:v1
给出了预期的输出:
run
run
run
...
然后,我使用kill命令向过程发送一个TERM信号(以普通的术语映射到Ctrl + C,并且是Java关闭钩子的默认触发器):
# pardon the fancy functions;
# they are quite useful for me when dealing with processes
function pid() {
ps -ef | grep $1 | grep -v grep | awk '{print $2}'
}
function killsig() {
for i in pid $2; do
sudo kill $1 $i
done
}
alias termit='killsig -15'
# with all the above in place, I just have to run:
termit Kill
正如预期的那样,关闭挂钩被调用并顺利执行。
再往前走,我把整个东西变成了一个独立的K8s pod (由一个单副本Deployment支持 ):
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kill
spec:
selector:
matchLabels:
k8s-app: kill
template:
metadata:
labels:
k8s-app: kill
spec:
containers:
- name: kill
image: kill:v1
并尝试了同样的事情,这个时候通过清空出spec.replicas通过(与我们做它在IPS) kubectl edit deployment命令,而不是手动kill -TERM :
kubectl edit deployment kill
# vi is my default editor
# set "replicas" to 0 (line 20 in my case)
# <ESC>:wq<ENTER>
同时将Pod的控制台尾放在一个单独的窗口中:
# fancy stuff again
function findapp() {
kubectl get pod -l k8s-app=$1 -oname | cut -b 6-;
}
function klog() {
kubectl logs -f findapp $1;
}
# the final command
klog kill
显示输出:
run
run
...
run
TERM
busy
Exception in thread "main" java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Kill.main(Kill.java:22)
busy
busy
busy
exit
该死,它仍然可以正常关闭!
那么我的ips-worker怎么了?
为了验证,我在IPS上运行了一个单副本集群,手动更改了映像( spec.template.spec.containers[0].image )和启动命令( spec.template.spec.containers[0].command )通过kubectl edit (保持所有其他因素(例如环境变量和体积安装)不变)对K8进行了部署,并尝试了相同的归零顺序;
结果一样! 正常关机!
然后我想到,虽然我的kill容器只使用了java Kill命令,但是ips-worker使用了一些更复杂的命令:
/bin/sh -c <copy some files> && <run some custom command> && <run ultraesb-x.sh>
在最后一部分中,我们构造(使用特制的类路径和一些JVM参数)并执行一个很长的java命令,以启动UltraESB-X野兽。
因此,最终,容器中的最终实时命令归结为:
/bin/sh -c <basepath>/ultraesb-x.sh
因此,我通过稍微更改Dockerfile在kill容器上尝试了shell命令:
通过稍微更改Dockerfile:
FROM openjdk:8-jre-alpine
ADD Kill*.class /
# note the missing brackets and quotes, so that the command gets the default /bin/sh -c prefix
ENTRYPOINT java Kill
和耶! 正常关机不再。 在Docker(docker docker stop )和K8s(副本零输出)中,Java进程被残酷地杀死。
进一步调查,我在Google的指导下被引导至该受欢迎的SE帖子 , 该帖子基本上说shell( sh )默认情况下不会将接收到的信号传递给其子进程。 建议的替代方法是将内部命令作为exec运行,基本上将父进程( sh )替换为子进程( java ,如果为kill ):
FROM openjdk:8-jre-alpine
ADD Kill*.class /
ENTRYPOINT exec java Kill
为了kill ,那立刻就成功了。
对于ips-worker情况有所不同,因为有两个级别的调用:容器的命令通过/bin/sh -c调用命令链,而内置的ultraesb-x.sh调用最终的java命令。 因此,我不得不在两个地方包括exec :
在命令链末尾:
/bin/sh -c \
<copy some files> && \
<run some custom command> && \
exec <basepath>/ultraesb-x.sh
再一次在ultraesb-x.sh :
# do some magic to compose the classpath and other info for ESB startup
exec $JAVA_HOME/bin/java <classpath and other params>
看起来很简单,这两个exec足以使ips-worker正常关闭,进而恢复到我们的Integration Platform 。
翻译自: https://www.javacodegeeks.com/2017/09/gracefully-shutting-java-containers-double-check.html
java中容器是什么