一、通过Dockerfile制作jar镜像
1、定义Dockerfile文件
提前准备好wenjay-eureka.jar文件
FROM java
VOLUME /tmp/java
ADD wenjay-eureka.jar eureka.jar
RUN bash -c 'touch /eureka.jar'
EXPOSE 7001
# Image创建容器时的初始化内存,最大内存,及启动时使用的profile. -c为清除以前启动的数据
ENTRYPOINT ["java","-Xms1024m","-Xmx1024m","-jar","/eureka.jar","--spring.profiles.active=dev","--spring.security.user.name=wenjay","--spring.security.user.password=wenjay","--server.port=7001","--eureka.instance.hostname=wenjay-eureka-01","-c"]
2、执行docker的镜像制作命令
使用docker build -t wenjay.io:5000/eureka .
注意后面的点号不能丢,代表当前目录,如果Dockerfile在其他位置,请根据实际目录调整
[root@wenjay define]# docker build -t wenjay.io:5000/eureka .
Sending build context to Docker daemon 53.62MB
Step 1/6 : FROM java
latest: Pulling from library/java
5040bd298390: Pull complete
fce5728aad85: Pull complete
76610ec20bf5: Pull complete
60170fec2151: Pull complete
e98f73de8f0d: Pull complete
11f7af24ed9c: Pull complete
49e2d6393f32: Pull complete
bb9cdec9c7f3: Pull complete
Digest: sha256:c1ff613e8ba25833d2e1940da0940c3824f03f802c449f3d1815a66b7f8c0e9d
Status: Downloaded newer image for java:latest
---> d23bdf5b1b1b
Step 2/6 : VOLUME /tmp/java
---> Running in 930fcee8e0b8
Removing intermediate container 930fcee8e0b8
---> da6c7c06b7a5
Step 3/6 : ADD wenjay-eureka.jar eureka.jar
---> 57ce21a3bd08
Step 4/6 : RUN bash -c 'touch /eureka.jar'
---> Running in 8051cad00d86
Removing intermediate container 8051cad00d86
---> b504f84903c1
Step 5/6 : EXPOSE 7001
---> Running in ca6d71dc8657
Removing intermediate container ca6d71dc8657
---> ac95d51897e5
Step 6/6 : ENTRYPOINT ["java","-Xms1024m","-Xmx1024m","-jar","/eureka.jar","--spring.profiles.active=dev","--spring.security.user.name=wenjay","--spring.security.user.password=wenjay","--server.port=7001","--eureka.instance.hostname=wenjay-eureka-01","-c"]
---> Running in 3e0610609dae
Removing intermediate container 3e0610609dae
---> 5cc9276293b4
Successfully built 5cc9276293b4
Successfully tagged wenjay.io:5000/eureka:latest
[root@wenjay define]#
3、查看镜像的详细
使用docker inspect <镜像名称>
[root@wenjay define]# docker inspect 545dcef31a62
[
{
"Id": "545dcef31a62e000c6e11be58d180e3cfbc32c023c95731829b076d8e5335e00",
"Created": "2021-12-28T08:35:22.760988429Z",
"Path": "java",
"Args": [
"-Xms1024m",
"-Xmx1024m",
"-jar",
"/eureka.jar",
"--spring.profiles.active=dev",
"--spring.security.user.name=wenjay",
"--spring.security.user.password=wenjay",
"--server.port=7001",
"--eureka.instance.hostname=wenjay-eureka-01",
"-c"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 13839,
"ExitCode": 0,
"Error": "",
"StartedAt": "2021-12-28T08:35:23.373647837Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:5cc9276293b4eeeeed2af18440240f37857f2c1a3aca9b195f9e65fd7b487cf6",
"ResolvConfPath": "/var/lib/docker/containers/545dcef31a62e000c6e11be58d180e3cfbc32c023c95731829b076d8e5335e00/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/545dcef31a62e000c6e11be58d180e3cfbc32c023c95731829b076d8e5335e00/hostname",
"HostsPath": "/var/lib/docker/containers/545dcef31a62e000c6e11be58d180e3cfbc32c023c95731829b076d8e5335e00/hosts",
"LogPath": "/var/lib/docker/containers/545dcef31a62e000c6e11be58d180e3cfbc32c023c95731829b076d8e5335e00/545dcef31a62e000c6e11be58d180e3cfbc32c023c95731829b076d8e5335e00-json.log",
"Name": "/loving_lehmann",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "",
"ExecIDs": null,
"HostConfig": {
"Binds": null,
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {
"max-size": "100m"
}
},
"NetworkMode": "default",
"PortBindings": {},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": null,
"CapDrop": null,
"CgroupnsMode": "host",
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"KernelMemory": 0,
"KernelMemoryTCP": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": false,
"PidsLimit": null,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": [
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware"
],
"ReadonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
},
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/23fc55466091669de39ae1f69f0248a5b45777c8b88fe876b0eb3e1742f80c87-init/diff:/var/lib/docker/overlay2/84bb40e8f02d6b159422dacc85e70811071a37193db69cae49066b74f78e7762/diff:/var/lib/docker/overlay2/39f9b31cd3aa93318c71717a1e8ff4f6c564327da5331b5982e24bc041985556/diff:/var/lib/docker/overlay2/31acefccd18d2890de5a3db1318ca4e1d5c51d0111a5ed9da870db560c1c1be3/diff:/var/lib/docker/overlay2/a72bcc970ad750a59021de24970d04599840ee524df891e5da4df3eaa00f9947/diff:/var/lib/docker/overlay2/24cf63c94d5aa4237a440528b95cd02b6bd83e629eca0320c37c53d03afd9f0b/diff:/var/lib/docker/overlay2/7051c06f42b4f2ab8aac0b211d0f0748f0173993b247963c7b5470f419f2fa79/diff:/var/lib/docker/overlay2/d5d78732c72cf93fa6b92430dc2e5a1c393c1a6f511864e7845542e5888ee67b/diff:/var/lib/docker/overlay2/053a5e6c86ddbf641fff52d6005ee8c1cf9da303fea6aae02ade7e6447d208e1/diff:/var/lib/docker/overlay2/e5891927a4f34465335db6a46425f84eaf1d090724b5346d8aed6d0154028f1f/diff:/var/lib/docker/overlay2/06e6db7800a7e0f65c5b30ef6bf7482ac295dc5e4a2eaf457d7e922b74f11f7b/diff",
"MergedDir": "/var/lib/docker/overlay2/23fc55466091669de39ae1f69f0248a5b45777c8b88fe876b0eb3e1742f80c87/merged",
"UpperDir": "/var/lib/docker/overlay2/23fc55466091669de39ae1f69f0248a5b45777c8b88fe876b0eb3e1742f80c87/diff",
"WorkDir": "/var/lib/docker/overlay2/23fc55466091669de39ae1f69f0248a5b45777c8b88fe876b0eb3e1742f80c87/work"
},
"Name": "overlay2"
},
"Mounts": [
{
"Type": "volume",
"Name": "f78738490fae81343a5ca20c8bb2662714d3ac6b266f09cf64ce9f240debaee5",
"Source": "/var/lib/docker/volumes/f78738490fae81343a5ca20c8bb2662714d3ac6b266f09cf64ce9f240debaee5/_data",
"Destination": "/tmp/java",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
"Config": {
"Hostname": "545dcef31a62",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"7001/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=C.UTF-8",
"JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64",
"JAVA_VERSION=8u111",
"JAVA_DEBIAN_VERSION=8u111-b14-2~bpo8+1",
"CA_CERTIFICATES_JAVA_VERSION=20140324"
],
"Cmd": null,
"Image": "wenjay.io:5000/eureka",
"Volumes": {
"/tmp/java": {}
},
"WorkingDir": "",
"Entrypoint": [
"java",
"-Xms1024m",
"-Xmx1024m",
"-jar",
"/eureka.jar",
"--spring.profiles.active=dev",
"--spring.security.user.name=wenjay",
"--spring.security.user.password=wenjay",
"--server.port=7001",
"--eureka.instance.hostname=wenjay-eureka-01",
"-c"
],
"OnBuild": null,
"Labels": {}
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "5112280ce667a5856f5ca3a2d370e223e6c9188fe4fe283a72fec895e98b7216",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
"7001/tcp": null
},
"SandboxKey": "/var/run/docker/netns/5112280ce667",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "c24e59964de108de8063d3596b050c3e7203b7b93b39fa0a3f646cb20331fcca",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.6",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:06",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "54d1fceedd268f2a1dccde75b614c4fb31c488b4b220f8879a422369e0f845e3",
"EndpointID": "c24e59964de108de8063d3596b050c3e7203b7b93b39fa0a3f646cb20331fcca",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.6",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:06",
"DriverOpts": null
}
}
}
}
]
[root@wenjay define]#
4、验证镜像
docker run启动镜像
[root@wenjay define]# docker run -d -p 8080:7001 -v /opt/eureka:/tmp/java --name wenjay_eureka wenjay.io:5000/eureka
3d569f2b63e36f5331d653d3b6d747650d36c78a17586035fafe469a22fdac4b
[root@wenjay define]#
[root@wenjay define]# docker logs wenjay_eureka
2021-12-28 08:44:25.572 INFO 1 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$4518bad] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.4.RELEASE)
2021-12-28 08:44:25.756 INFO 1 --- [ main] com.wenjay.eureka.EurekaApplication : The following profiles are active: dev
2021-12-28 08:44:26.889 WARN 1 --- [ main] o.s.boot.actuate.endpoint.EndpointId : Endpoint ID 'service-registry' contains invalid characters, please migrate to a valid format.
2021-12-28 08:44:27.245 INFO 1 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=73abebd2-fee3-3511-b85d-7a0c81e6d174
2021-12-28 08:44:27.455 INFO 1 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$4518bad] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-12-28 08:44:27.909 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 7001 (http)
2021-12-28 08:44:27.932 INFO 1 --- [ main] o.a.coyote.http11.Http11NioProtocol : Initializing ProtocolHandler ["http-nio-7001"]
2021-12-28 08:44:27.953 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-12-28 08:44:27.953 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.17]
页面访问eureka
5、进入容器内部查看
/var/local/logs/logs/wenjay-eureka为java包eureka.jar配置参数设定的日志目录
[root@wenjay define]# nsenter --target 14361 --mount --uts --ipc --net --pid
root@3d569f2b63e3:/# cd /var/local/logs/logs/wenjay-eureka/
root@3d569f2b63e3:/var/local/logs/logs/wenjay-eureka# ls
error.log server.log
root@3d569f2b63e3:/var/local/logs/logs/wenjay-eureka#
6、其他
1、如何进入容器:
- docker attach [容器ID]
- nsenter
先用inspect获得容器的第一个进程的PID:
docker inspect --format "{{.State.Pid}}" wenjay_registry
使用命令nsenter 进入nsenter --target 11935 --mount --uts --ipc --net --pid
2、nsenter命令简介:
nsenter命令是一个可以在指定进程的命令空间下运行指定程序的命令。它位于util-linux包中。
用途
一个最典型的用途就是进入容器的网络命令空间。相当多的容器为了轻量级,是不包含较为基础的命令的,比如说ip address
,ping
,telnet
,ss
,tcpdump
等等命令,这就给调试容器网络带来相当大的困扰:只能通过docker inspect ContainerID
命令获取到容器IP,以及无法测试和其他网络的连通性。这时就可以使用nsenter命令仅进入该容器的网络命名空间,使用宿主机的命令调试容器网络。
此外,nsenter也可以进入mnt
, uts
, ipc
, pid
, user
命令空间,以及指定根目录和工作目录。
使用
首先看下nsenter命令的语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | nsenter [options] [program [arguments]] options: -t, --target pid:指定被进入命名空间的目标进程的pid -m, --mount[=file]:进入mount命令空间。如果指定了file,则进入file的命令空间 -u, --uts[=file]:进入uts命令空间。如果指定了file,则进入file的命令空间 -i, --ipc[=file]:进入ipc命令空间。如果指定了file,则进入file的命令空间 -n, --net[=file]:进入net命令空间。如果指定了file,则进入file的命令空间 -p, --pid[=file]:进入pid命令空间。如果指定了file,则进入file的命令空间 -U, --user[=file]:进入user命令空间。如果指定了file,则进入file的命令空间 -G, --setgid gid:设置运行程序的gid -S, --setuid uid:设置运行程序的uid -r, --root[=directory]:设置根目录 -w, --wd[=directory]:设置工作目录 如果没有给出program,则默认执行$SHELL。 |
示例:
运行一个nginx容器,查看该容器的pid:
1 2 | [root@staight ~]# docker inspect -f {{.State.Pid}} nginx 5645 |
然后,使用nsenter命令进入该容器的网络命令空间:
1 2 3 4 5 6 7 8 9 10 | [root@staight ~]# nsenter -n -t5645 [root@staight ~]# ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever |
进入成功~
在Kubernetes中,在得到容器pid之前还需获取容器的ID,可以使用如下命令获取:
1 2 | [root@node1 test]# kubectl get pod test -oyaml|grep containerID - containerID: docker://cf0873782d587dbca6aa32f49605229da3748600a9926e85b36916141597ec85 |
或者更为精确地获取containerID:
1 2 | [root@node1 test]# kubectl get pod test -o template --template='{{range .status.containerStatuses}}{{.containerID}}{{end}}' docker://cf0873782d587dbca6aa32f49605229da3748600a9926e85b36916141597ec85 |
原理
namespace
namespace是Linux中一些进程的属性的作用域,使用命名空间,可以隔离不同的进程。
Linux在不断的添加命名空间,目前有:
- mount:挂载命名空间,使进程有一个独立的挂载文件系统,始于Linux 2.4.19
- ipc:ipc命名空间,使进程有一个独立的ipc,包括消息队列,共享内存和信号量,始于Linux 2.6.19
- uts:uts命名空间,使进程有一个独立的hostname和domainname,始于Linux 2.6.19
- net:network命令空间,使进程有一个独立的网络栈,始于Linux 2.6.24
- pid:pid命名空间,使进程有一个独立的pid空间,始于Linux 2.6.24
- user:user命名空间,是进程有一个独立的user空间,始于Linux 2.6.23,结束于Linux 3.8
- cgroup:cgroup命名空间,使进程有一个独立的cgroup控制组,始于Linux 4.6
Linux的每个进程都具有命名空间,可以在/proc/PID/ns目录中看到命名空间的文件描述符。
1 2 3 4 5 6 7 8 9 10 | [root@staight ns]# pwd /proc/1/ns [root@staight ns]# ll total 0 lrwxrwxrwx 1 root root 0 Sep 23 19:53 ipc -> ipc:[4026531839] lrwxrwxrwx 1 root root 0 Sep 23 19:53 mnt -> mnt:[4026531840] lrwxrwxrwx 1 root root 0 Sep 23 19:53 net -> net:[4026531956] lrwxrwxrwx 1 root root 0 Sep 23 19:53 pid -> pid:[4026531836] lrwxrwxrwx 1 root root 0 Sep 23 19:53 user -> user:[4026531837] lrwxrwxrwx 1 root root 0 Sep 23 19:53 uts -> uts:[4026531838] |
clone
clone是Linux的系统调用函数,用于创建一个新的进程。
clone和fork比较类似,但更为精细化,比如说使用clone创建出的子进程可以共享父进程的虚拟地址空间,文件描述符表,信号处理表等等。不过这里要强调的是,clone函数还能为新进程指定命名空间。
clone的语法:
1 2 3 4 5 6 | #define _GNU_SOURCE #include <sched.h> int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *ptid, void *newtls, pid_t *ctid */ ); |
其中flags即可指定命名空间,包括:
- CLONE_NEWCGROUP:cgroup
- CLONE_NEWIPC:ipc
- CLONE_NEWNET:net
- CLONE_NEWNS:mount
- CLONE_NEWPID:pid
- CLONE_NEWUSER:user
- CLONE_NEWUTS:uts
使用示例:
1 | pid = clone(childFunc, stackTop, CLONE_NEWUTS | SIGCHLD, argv[1]); |
setns
clone用于创建新的命令空间,而setns则用来让当前线程(单线程即进程)加入一个命名空间。
语法:
1 2 3 4 5 6 7 8 | #define _GNU_SOURCE /* See feature_test_macros(7) */ #include <sched.h> int setns(int fd, int nstype); fd参数是一个指向一个命名空间的文件描述符,位于/proc/PID/ns/目录。 nstype指定了允许进入的命名空间,一般可设置为0,表示允许进入所有命名空间。 |
因此,往往该函数的用法为:
- 调用setns函数:指定该线程的命名空间。
- 调用execvp函数:执行指定路径的程序,创建子进程并替换父进程。
这样,就可以指定命名空间运行新的程序了。
代码示例:
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 | #define _GNU_SOURCE #include <fcntl.h> #include <sched.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ } while (0) int main(int argc, char *argv[]) { int fd; if (argc < 3) { fprintf(stderr, "%s /proc/PID/ns/FILE cmd args...\n", argv[0]); exit(EXIT_FAILURE); } fd = open(argv[1], O_RDONLY); /* Get file descriptor for namespace */ if (fd == -1) errExit("open"); if (setns(fd, 0) == -1) /* Join that namespace */ errExit("setns"); execvp(argv[2], &argv[2]); /* Execute a command in namespace */ errExit("execvp"); } |
使用示例:
1 | ./ns_exec /proc/3550/ns/uts /bin/bash |
nsenter
那么,最后就是nsenter了,nsenter相当于在setns的示例程序之上做了一层封装,使我们无需指定命名空间的文件描述符,而是指定进程号即可。
指定进程号PID以及需要进入的命名空间后,nsenter会帮我们找到对应的命名空间文件描述符/proc/PID/ns/FD
,然后使用该命名空间运行新的程序。
参考文档
容器内抓包定位网络问题:容器内抓包定位网络问题 · TKE Handbook
man-page:nsenter:nsenter(1) - Linux manual page
man-page:clone:clone(2) - Linux manual page
man-page:setns:setns(2) - Linux manual page