k8s(二)Docker的HelloWorld
- May 12, 2022
Build,Ship and Run Any App,Anywhere
作者:lomtom
个人网站:lomtom.cn 🔗
个人公众号:博思奥园 🔗
你的支持就是我最大的动力。
k8s系列:
docker安装
docker的安装不在本节的重点范围之内。
- win参考:docker 安装
- linux直接
yum -y install docker-ce-19.03.15 docker-ce-cli-19.03.15
一条命令搞定
dokcer实操
构建镜像
部署一个Go项目,该项目有一个接口/
返回ok
FROM golang:alpine AS builder
RUN mkdir /app
WORKDIR /app
COPY . .
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN go mod tidy
Run go build main.go -o demo-gin
CMD ["./demo-gin"]
会发现他非常的大,足足有300M,这显然不符合我们的预期。
究其原因是基础镜像太大了,如果想要这个镜像变小应该怎么做?
那么我们可以先构建好,再将构建好的程序放到我们最终的容器中就可以了。这里有两种方法:
- 可以自己编译,再打包,这样一致性不太好,因为如果你在win中编译的放在linux并不能用;
- 在打包之前用一个镜像来编译,编译后复制到最终的镜像内
所以果断采取第二种方法:
FROM golang:alpine AS builder
RUN mkdir /app
WORKDIR /app
COPY . .
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN go mod tidy
Run go build main.go -o demo-gin
FROM alpine
WORKDIR /app
COPY --from=builder /app/demo-gin /app/demo-gin
CMD ["./demo-gin"]
在项目目录下使用docker build
进行编译:
[root@master demo-gin]# docker build -t demo-gin .
Sending build context to Docker daemon 9.728kB
Step 1/11 : FROM golang:alpine AS builder
---> dd6fd110e957
Step 2/11 : RUN mkdir /app
---> Using cache
---> 0b6745385c76
Step 3/11 : WORKDIR /app
---> Using cache
---> 244a453fc1b5
Step 4/11 : COPY . .
---> 8238e479ac95
Step 5/11 : RUN go env -w GOPROXY=https://goproxy.cn,direct
---> Running in b3ba7bddda82
Removing intermediate container b3ba7bddda82
---> f6705d03f979
Step 6/11 : RUN go mod tidy
---> Running in 32537fd42323
go: downloading github.com/gin-gonic/gin v1.7.7
go: downloading github.com/gin-contrib/sse v0.1.0
go: downloading github.com/mattn/go-isatty v0.0.12
go: downloading github.com/golang/protobuf v1.3.3
go: downloading github.com/stretchr/testify v1.4.0
go: downloading github.com/json-iterator/go v1.1.9
go: downloading github.com/go-playground/validator/v10 v10.4.1
go: downloading github.com/ugorji/go/codec v1.1.7
go: downloading gopkg.in/yaml.v2 v2.2.8
go: downloading golang.org/x/sys v0.0.0-20200116001909-b77594299b42
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading github.com/pmezard/go-difflib v1.0.0
go: downloading github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421
go: downloading github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
go: downloading github.com/go-playground/universal-translator v0.17.0
go: downloading github.com/leodido/go-urn v1.2.0
go: downloading github.com/ugorji/go v1.1.7
go: downloading golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
go: downloading github.com/go-playground/assert/v2 v2.0.1
go: downloading github.com/go-playground/locales v0.13.0
go: downloading gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
Removing intermediate container 32537fd42323
---> d117b4852e6a
Step 7/11 : Run go build -o demo-gin main.go
---> Running in 5acd88af4878
Removing intermediate container 5acd88af4878
---> f4153d9c7a5e
Step 8/11 : FROM alpine
latest: Pulling from library/alpine
df9b9388f04a: Already exists
Digest: sha256:4edbd2beb5f78b1014028f4fbb99f3237d9561100b6881aabbf5acce2c4f9454
Status: Downloaded newer image for alpine:latest
---> 0ac33e5f5afa
Step 9/11 : WORKDIR /app
---> Running in 55c0e0652368
Removing intermediate container 55c0e0652368
---> 88534de5b753
Step 10/11 : COPY --from=builder /app/demo-gin /app/demo-gin
---> 229f0992f605
Step 11/11 : CMD ["./demo-gin"]
---> Running in e6ac1b4bbc1d
Removing intermediate container e6ac1b4bbc1d
---> 74b0086fe7c6
Successfully built 74b0086fe7c6
Successfully tagged demo-gin:latest
docker build 操作完成后,我可以通过 docker images 命令查看结果:
[root@master demo-gin]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
demo-gin latest 74b0086fe7c6 1 minutes ago 14.8MB
最终镜像大小只有14.8MB。接下来,我使用这个镜像,通过 docker run 命令启动容器:
[root@master demo-gin]# docker run -p 3001:8080 demo-gin
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET / --> main.main.func1 (1 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080
同时,使用-p 3001:8080
参数将容器内的8080端口映射到宿主机器上的3001端口,这样就可以通过宿主机器的3001端口访问容器的8080端口。
后台运行
如果需要后台运行,在run后面加上 -d
参数即可
随后通过暴露的端口访问。
lomtom@atongmudeMacBook ~ % curl http://8.16.0.67:3001
"ok"%
挂载卷
如何在运行的时候,将宿主机器的目录挂载在容器内呢?
可以使用-v参数进行指定:-v /root/lomtom/demo-gin/volume:/test
,这里将宿主机器的/root/lomtom/demo-gin/volume
目录挂载在容器的/test
目录下,这样在容器内就可以访问到宿主机器的/root/lomtom/demo-gin/volume
目录。
[root@master demo-gin]# docker run -p 3001:8080 -v /root/lomtom/demo-gin/volume:/test -d demo-gin
在宿主机器中的/root/lomtom/demo-gin/volume
添加一个test.txt
文件,内容为 are you happy today?
[root@master volume]# cat /root/lomtom/demo-gin/volume/test.txt
are you happy today?
回到容器中进行查看,发现/test
下存在一个test.txt
文件。
/ # cat test/test.txt
are you happy today?
这就是Docker 的Volume 机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作。
除此之外,也可以不指定宿主机器的目录。
[root@master demo-gin]# docker run -p 3001:8080 -v /test -d demo-gin
容器启动之后,可以查看volume的信息
[root@master volume]# docker volume ls
DRIVER VOLUME NAME
local 4605aba6ab35518ce29fc650e75c157d3c4b95451123a9687d93eed5cf6be6b8
容器内/test
目录下的文件就会保存在宿主机器的/var/lib/docker/volumes/4605aba6ab35518ce29fc650e75c157d3c4b95451123a9687d93eed5cf6be6b8/_data/
中。
值得注意的是,如果使用该容器重新构建一个镜像,容器 Volume 里的信息,并不会被 docker commit 提交掉;但这个挂载点目录 /test 本身,则会出现在新的镜像当中。
docker分析
镜像
回归到镜像本身,我们使用docker inspect [镜像 ID]
来查看镜像属性。
[root@master ~]# docker inspect 74b0086fe7c6
......
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/990ec90661e0868c1fda29618916e4c5e3143a61394055de6e3f1333de9453fe/diff:/var/lib/docker/overlay2/69c24a49f77b361d64ddf7ce24051acee85d44cf27a62affee17203b9eb82d94/diff",
"MergedDir": "/var/lib/docker/overlay2/d6f217c3a337c34b5189700a3f94e521b6bde6b0309119f62abe1b51efb024be/merged",
"UpperDir": "/var/lib/docker/overlay2/d6f217c3a337c34b5189700a3f94e521b6bde6b0309119f62abe1b51efb024be/diff",
"WorkDir": "/var/lib/docker/overlay2/d6f217c3a337c34b5189700a3f94e521b6bde6b0309119f62abe1b51efb024be/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:4fc242d58285699eca05db3cc7c7122a2b8e014d9481f323bd9277baacfa0628",
"sha256:38413451f0dea7413dfa86b80c259d3e2aa3ac35c64982c5a0df1d5e2901477f",
"sha256:499f9544bfdd5dcddab710c6fbbcc3915731ac3f1fe2e338e7217be7a6bd4737"
]
},
......
其中有几个重点属性需要关注一下。
-
RootFS属性,layers就是代表镜像的层级,其中有三层(三条数据)
再使用
docker history
分析一下镜像的结构[root@master ~]# docker history 74b0086fe7c6 --no-trunc=true IMAGE CREATED CREATED BY SIZE COMMENT sha256:74b0086fe7c6316790a203895e879d387d00d7f733af9060dd9ff1058c39a068 6 days ago /bin/sh -c #(nop) CMD ["./demo-gin"] 0B <missing> 6 days ago /bin/sh -c #(nop) COPY file:cd53aab436246ffa2e579600132d18c644f289099eced8fe82b285be3861eec9 in /app/demo-gin 9.19MB <missing> 6 days ago /bin/sh -c #(nop) WORKDIR /app 0B <missing> 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 2 weeks ago /bin/sh -c #(nop) ADD file:5d6753d25da3a14ce1f6cf66e4c7fd4f4b85a3759a9d93efb3fd9ff852b5b56e4 in / 5.57MB
或者你在pull镜像的时候留意一下
[root@master ~]# docker pull lomtom/demo-gin Using default tag: latest latest: Pulling from lomtom/demo-gin df9b9388f04a: Pull complete 2f0488c48942: Pull complete 06e8ee5ba102: Pull complete Digest: sha256:3ea098ee7ec35c266092f94e5c90e1edceb64d91863c2533fc5869eb8c319581 Status: Downloaded newer image for lomtom/demo-gin:latest docker.io/lomtom/demo-gin:latest
不难发现,执行
docker history
之后,根据日期6 days ago,就能知道这三层就是我们自定义的dockerfile生成的三层。那么 2 weeks ago 的两层是哪里来的呢?
去查看使用的基础镜像 🔗,不难发现他的最新更新恰好就是两周前。
那么,这三层是怎么和Dockerfile对应的呢。
会看我们真实用到的dockerfile文件
FROM alpine WORKDIR /app COPY --from=builder /app/demo-gin /app/demo-gin CMD ["./demo-gin"]
- FROM选取基础镜像,也就是
FROM alpine
,构成了层级的第一层和第二层。 WORKDIR /app
对应了第三层,以及他的CREATE BY
为/bin/sh -c #(nop) WORKDIR /app
- …
- FROM选取基础镜像,也就是
-
其次可以关注
GraphDriver
属性。-
Name标明了该镜像的
UnionFS
,即overlay2文件系统。 -
Data属性中的LowerDir、MergedDir、UpperDir
-
LowerDir (只读):只读的 image layer,其实就是 rootfs, 在使用 Dockfile 构建镜像的时候就定好了
-
Upperdir (读写):upperdir 则是在 lowerdir 之上的一层, 为读写层。容器在启动的时候会创建, 所有对容 器的修改, 都是在这层。比如容器启动写入的日志文件,或者是应用程序写入的临时文件
-
MergedDir (展示):merged 目录是容器的挂载点,在用户视角能够看到的所有文件
-
-
容器
-
GraphDriver
同样可以使用
docker inspect [容器 ID]
来查看容器属性。..... "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/ea16bd86de8b3c254a2c1582f77ffd9910485422d837aaa445acba6f71192920-init/diff:/var/lib/docker/overlay2/d6f217c3a337c34b5189700a3f94e521b6bde6b0309119f62abe1b51efb024be/diff:/var/lib/docker/overlay2/990ec90661e0868c1fda29618916e4c5e3143a61394055de6e3f1333de9453fe/diff:/var/lib/docker/overlay2/69c24a49f77b361d64ddf7ce24051acee85d44cf27a62affee17203b9eb82d94/diff", "MergedDir": "/var/lib/docker/overlay2/ea16bd86de8b3c254a2c1582f77ffd9910485422d837aaa445acba6f71192920/merged", "UpperDir": "/var/lib/docker/overlay2/ea16bd86de8b3c254a2c1582f77ffd9910485422d837aaa445acba6f71192920/diff", "WorkDir": "/var/lib/docker/overlay2/ea16bd86de8b3c254a2c1582f77ffd9910485422d837aaa445acba6f71192920/work" }, "Name": "overlay2" }, ......
容器也和镜像一样存在LowerDir、MergedDir、UpperDir,并且这些目录能够在宿主机器上直接访问的。
之前构建镜像的dockerfile文件,所做的就是将
demo-gin
存放在/app
目录下,不出意外的话我们能够在MergedDir
目录下看到这个文件。[root@master ~]# ls /var/lib/docker/overlay2/ea16bd86de8b3c254a2c1582f77ffd9910485422d837aaa445acba6f71192920/merged/app demo-gin
接着我们进入容器,在容器内执行一些操作,例如在
/app
下创建一个文件。[root@master ~]# docker exec -it 4618b0cc2498 /bin/sh /app # ls demo-gin /app # mkdir lomtom /app # ls demo-gin lomtom
那么,此时容器内的
/app
下存在demo-gin lomtom。我们再在宿主机器上查看merged目录。[root@master ~]# ls /var/lib/docker/overlay2/ea16bd86de8b3c254a2c1582f77ffd9910485422d837aaa445acba6f71192920/merged/app demo-gin lomtom
-
Namespace 及 Cgroup
前一节已经对docker原理进行讲解,那么对于真实的容器,他的Namespace 及 Cgroup如何查看呢。
首先,找到该容器的进程编号,因为一个容器就是一个进程。
[root@master ~]# ps -aux| grep demo root 23213 0.0 0.0 112676 988 pts/0 S+ 10:33 0:00 grep --color=auto demo root 25199 0.0 0.0 709460 9156 ? Ssl 09:34 0:00 ./demo-gin
那么25199就是该容器的进程编号。使用
ls /proc/25199/
即可查看该进程下的一些资源信息。[root@master ~]# ls /proc/25199/ attr cgroup comm cwd fd io map_files mountinfo net oom_adj pagemap root sessionid stack status timers autogroup clear_refs coredump_filter environ fdinfo limits maps mounts ns oom_score personality sched setgroups stat syscall uid_map auxv cmdline cpuset exe gid_map loginuid mem mountstats numa_maps oom_score_adj projid_map schedstat smaps statm task wchan
其中就包括Namespace即ns。
[root@master ~]# ls /proc/25199/ns/ -l 总用量 0 lrwxrwxrwx. 1 root root 0 4月 21 09:36 ipc -> ipc:[4026532696] lrwxrwxrwx. 1 root root 0 4月 21 09:36 mnt -> mnt:[4026532694] lrwxrwxrwx. 1 root root 0 4月 21 09:34 net -> net:[4026532699] lrwxrwxrwx. 1 root root 0 4月 21 09:36 pid -> pid:[4026532697] lrwxrwxrwx. 1 root root 0 4月 21 10:30 user -> user:[4026531837] lrwxrwxrwx. 1 root root 0 4月 21 09:36 uts -> uts:[4026532695]
以及文件资源,即root,是不是验证了与容器中的文件一致
[root@master ~]# ls /proc/25199/root/app/ demo-gin lomtom
同样我们进入容器,在
/app
,新建一个文件config,并且写入hello world
,同样在宿主机器上能够查看内容。/app # echo hello world > config /app # ls config demo-gin lomtom [root@master ~]# cat /proc/25199/root/app/config hello world