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

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"
            ]
        },
......

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

  1. 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 的两层是哪里来的呢?

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

    image-20220421100238179

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

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

    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]来查看容器属性。

    .....
    "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
    
  2. 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
    
lomtom

标题:k8s(二)Docker的HelloWorld

作者:lomtom

链接:https://lomtom.cn/ffa3efb0