k8s(二)Docker的HelloWorld

k8s(二)Docker的HelloWorld

Build,Ship and Run Any App,Anywhere

作者:lomtom

个人网站:lomtom.cn

个人公众号:博思奥园

你的支持就是我最大的动力。

k8s系列:

  1. k8s(一)走进docker
  2. k8s(二)Docker的HelloWorld
  3. k8s(三)走进k8s
  4. k8s(四)安装k8s集群
  5. k8s(五)k8s的HelloWorld
  6. k8s(六)走进Pod

docker
Build,Ship and Run Any App,Anywhere

docker安装

docker的安装不在本节的重点范围之内。

  1. win参考:docker 安装
  2. linux直接yum -y install docker-ce-19.03.15 docker-ce-cli-19.03.15一条命令搞定

dokcer实操

构建镜像

部署一个Go项目,该项目有一个接口/返回ok

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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并不能用;
  • 在打包之前用一个拿一个镜像来编译,编译后复制到最终的镜像内

所以果断采取第二种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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进行编译:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[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 命令查看结果:

1
2
3
[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 命令启动容器:

1
2
3
4
5
6
7
8
9
[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 参数即可

随后通过暴露的端口访问。

1
2
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目录。

1
[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?

1
2
[root@master volume]# cat /root/lomtom/demo-gin/volume/test.txt 
are you happy today?

回到容器中进行查看,发现/test下存在一个test.txt文件。

1
2
/ # cat test/test.txt 
are you happy today?

这就是Docker 的Volume 机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作。

除此之外,也可以不指定宿主机器的目录。

1
[root@master demo-gin]# docker run -p 3001:8080 -v /test -d demo-gin

容器启动之后,可以查看volume的信息

1
2
3
[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]来查看镜像属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[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"
]
},
......

其中有几个重点属性需要关注一下。

  1. RootFS属性,layers就是代表镜像的层级,其中有三层(三条数据)

    再使用docker history分析一下镜像的结构

    1
    2
    3
    4
    5
    6
    7
    [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镜像的时候留意一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [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 的两层是哪里来的呢?

    去查看使用的基础镜像,不难发现他的最新更新恰好就是两周前。

    image-20220421100238179

    那么,这三层是怎么和Dockerfile对应的呢。

    会看我们真实用到的dockerfile文件

    1
    2
    3
    4
    5
    6
    FROM alpine

    WORKDIR /app
    COPY --from=builder /app/demo-gin /app/demo-gin

    CMD ["./demo-gin"]
    1. FROM选取基础镜像,也就是FROM alpine,构成了层级的第一层和第二层。
    2. WORKDIR /app对应了第三层,以及他的CREATE BY/bin/sh -c #(nop) WORKDIR /app
  2. 其次可以关注GraphDriver属性。

    • Name标明了该镜像的UnionFS,即overlay2文件系统。

    • Data属性中的LowerDir、MergedDir、UpperDir

      • LowerDir (只读):只读的 image layer,其实就是 rootfs, 在使用 Dockfile 构建镜像的时候就定好了

      • Upperdir (读写):upperdir 则是在 lowerdir 之上的一层, 为读写层。容器在启动的时候会创建, 所有对容 器的修改, 都是在这层。比如容器启动写入的日志文件,或者是应用程序写入的临时文件

      • MergedDir (展示):merged 目录是容器的挂载点,在用户视角能够看到的所有文件

容器

  1. GraphDriver

    同样可以使用docker inspect [容器 ID]来查看容器属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    .....
    "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目录下看到这个文件。

    1
    2
    [root@master ~]# ls /var/lib/docker/overlay2/ea16bd86de8b3c254a2c1582f77ffd9910485422d837aaa445acba6f71192920/merged/app
    demo-gin

    接着我们进入容器,在容器内执行一些操作,例如在/app下创建一个文件。

    1
    2
    3
    4
    5
    6
    [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目录。

    1
    2
    [root@master ~]# ls /var/lib/docker/overlay2/ea16bd86de8b3c254a2c1582f77ffd9910485422d837aaa445acba6f71192920/merged/app
    demo-gin lomtom
  2. Namespace 及 Cgroup

    前一节已经对docker原理进行讲解,那么对于真实的容器,他的Namespace 及 Cgroup如何查看呢。

    首先,找到该容器的进程编号,因为一个容器就是一个进程。

    1
    2
    3
    [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/即可查看该进程下的一些资源信息。

    1
    2
    3
    4
    [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。

    1
    2
    3
    4
    5
    6
    7
    8
    [root@master ~]# ls /proc/25199/ns/ -l
    总用量 0
    lrwxrwxrwx. 1 root root 0 421 09:36 ipc -> ipc:[4026532696]
    lrwxrwxrwx. 1 root root 0 421 09:36 mnt -> mnt:[4026532694]
    lrwxrwxrwx. 1 root root 0 421 09:34 net -> net:[4026532699]
    lrwxrwxrwx. 1 root root 0 421 09:36 pid -> pid:[4026532697]
    lrwxrwxrwx. 1 root root 0 421 10:30 user -> user:[4026531837]
    lrwxrwxrwx. 1 root root 0 421 09:36 uts -> uts:[4026532695]

    以及文件资源,即root,是不是验证了与容器中的文件一致

    1
    2
    [root@master ~]#  ls /proc/25199/root/app/
    demo-gin lomtom

    同样我们进入容器,在/app,新建一个文件config,并且写入hello world,同样在宿主机器上能够查看内容。

    1
    2
    3
    4
    5
    6
    7
    /app # echo hello world > config
    /app # ls
    config demo-gin lomtom


    [root@master ~]# cat /proc/25199/root/app/config
    hello world

k8s(二)Docker的HelloWorld

https://lomtom.cn/ffa3efb0.html

作者

lomtom

发布于

2022-05-12

更新于

2022-06-27

许可协议