Docker学习笔记(一)

Docker学习笔记(一)

暑期加入了沃天宇老师的实验室进行暑期的实习。在正式开始工作之前,师兄先让我了解一下技术栈,需要了解的有docker、k8s、springboot、springcloud。

仅以一系列博客记录一下自己学习的笔记。更多内容见Github

2021/7/5

参考资料

什么是docker?

在开始学习之前,先连带猜测给docker下一个定义,深入学习之后再回头来验证一下。

docker的作用应当是解决程序的环境问题,通过将环境和程序一起打包,使得能够方便地在不同计算机上恢复程序所需要的环境并运行程序。

这将解决开发过程中的环境不一致问题,大大降低部署时的环境问题。

相比于虚拟机,docker应当具有较高的效率和较低的代价。

安装

Linux参考:https://docs.docker.com/engine/install/

Windows参考:https://docs.docker.com/docker-for-windows/install/

注意Windows安装的时候需要打开WSL2功能,并且升级至最新版本。

运行官方demo

下载

首先,下载官方的示例app,将仓库中多余部分删除,保留app文件夹,并重命名为gov-sample-app

Dockerfile

创建Dockerfile:

# syntax=docker/dockerfile:1
FROM node:12-alpine
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
  1. 第一句,FROM语句制定了初始的image,由于本地没有这个image,所以会从仓库中下载;
  2. 第二句,RUN,应当是指定了一些需要使用的包,到这一步应当也是下载安装,作用应当是作为第一句引入的image的补充,添补额外的应用;
  3. 第三句,看起来应当是指定一个运行目录,但是这个目录是容器内的目录还是

Build

然后切换到gov-sample-app文件夹下运行:

docker build -t getting-started .

这个命令通过刚刚创建的Dockerfile来创建一个docker镜像。其中-t指令为该镜像指定一个tag,我们运行这个镜像的时候可以通过这个tag来指代这个镜像。

此时先开始下载所需要的依赖,包括node:12-alpine镜像和后面的python g++ make。但是此时发现下载速度感人,需要进行换源。

因为我是在自己的阿里云服务器上进行实验,所以换了阿里的源https://cr.console.aliyun.com/cn-beijing/instances/mirrors

重试发现仍不奏效,应当是第二步中apk add的问题。简单搜索之后了解到,这里使用的镜像Alpine其实是一个Linux发行版,因为其具有轻量化的优点,所以经常作为docker容器中使用的OS。因此,造成目前卡顿的原因是Alpine的包管理工具apk从仓库下载速度过慢,应当对apk进行换源。

在第一句FROM后再加一行以进行换源:

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories 

虽然还是有些慢,但是好多了。最后Build成功,以下为输出:

第一次Biuld:

$ sudo docker build -t getting-started .
Sending build context to Docker daemon  4.659MB
Step 1/7 : FROM node:12-alpine
 ---> deeae3752431
Step 2/7 : RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
 ---> Running in bb1b2b2fca8d
Removing intermediate container bb1b2b2fca8d
 ---> 306dcbb9fb1b
Step 3/7 : RUN apk add --no-cache python g++ make
 ---> Running in b9c56ec12a6b
fetch http://mirrors.aliyun.com/alpine/v3.11/main/x86_64/APKINDEX.tar.gz
fetch http://mirrors.aliyun.com/alpine/v3.11/community/x86_64/APKINDEX.tar.gz
(1/21) Installing binutils (2.33.1-r1)
(2/21) Installing gmp (6.1.2-r1)
(3/21) Installing isl (0.18-r0)
(4/21) Installing libgomp (9.3.0-r0)
(5/21) Installing libatomic (9.3.0-r0)
(6/21) Installing mpfr4 (4.0.2-r1)
(7/21) Installing mpc1 (1.1.0-r1)
(8/21) Installing gcc (9.3.0-r0)
(9/21) Installing musl-dev (1.1.24-r3)
(10/21) Installing libc-dev (0.7.2-r0)
(11/21) Installing g++ (9.3.0-r0)
(12/21) Installing make (4.2.1-r2)
(13/21) Installing libbz2 (1.0.8-r1)
(14/21) Installing expat (2.2.9-r1)
(15/21) Installing libffi (3.2.1-r6)
(16/21) Installing gdbm (1.13-r1)
(17/21) Installing ncurses-terminfo-base (6.1_p20200118-r4)
(18/21) Installing ncurses-libs (6.1_p20200118-r4)
(19/21) Installing readline (8.0.1-r0)
(20/21) Installing sqlite-libs (3.30.1-r2)
(21/21) Installing python2 (2.7.18-r0)
Executing busybox-1.31.1-r10.trigger
OK: 212 MiB in 37 packages
Removing intermediate container b9c56ec12a6b
 ---> 30167547cd72
Step 4/7 : WORKDIR /app
 ---> Running in 28935dd6ee9b
Removing intermediate container 28935dd6ee9b
 ---> 9b4f5c2f993f
Step 5/7 : COPY . .
 ---> 9e6b8013cc6f
Step 6/7 : RUN yarn install --production
 ---> Running in ffcc047905f0
yarn install v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
info There appears to be trouble with your network connection. Retrying...
info fsevents@1.2.9: The platform "linux" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 93.45s.
Removing intermediate container ffcc047905f0
 ---> 4f85bb916dc2
Step 7/7 : CMD ["node", "src/index.js"]
 ---> Running in 05444490086d
Removing intermediate container 05444490086d
 ---> 302e7c6b161a
Successfully built 302e7c6b161a
Successfully tagged getting-started:latest

到此,我们知道了,docker是需要一个os的,而一个常用的选择就是轻量的Linux发行版Alpine。我们通过Dockerfile来指定一个镜像如何打包,其中FROM指令来指定一个初始的镜像,类似于OOP中的继承,我们在这个镜像的基础上,通过一系列指令来丰富其内容,创建我们的镜像。

但是,仍旧有遗留的问题:

  1. RUNCMD有什么区别,它们的参数看起来都是在Alpine中运行的指令,这里的示例中它们采用了不同的格式,前者为shell格式,后者为exec格式,但是我试验后发现,将其格式对调,没有任何报错;
  2. COPY到底拷贝了什么;
  3. WORKDIR是哪儿来的?目前来看,官方包里的示例代码就是在app文件夹下,但是我这里将其改名为gov-sample-app,但是Build阶段并没有报错;
  4. Build结束后应当有一个image,但是我并没有在文件夹中看到新的文件,这个镜像保存在哪里了?
  5. 在试验遗留问题(1)的时候,我发现此时执行Build时,让我之前头疼的apk add的过程被跳过了,直接Using cache,并且可以发现,我只改动了Step6和7,对于前面的5步,除FROMCOPY外,都直接Using cache了(从windows的log来看,FROM也直接使用了本地的cache中的内容),看来docker似乎是对Dockerfile中的大部分步骤都一步一步地进行了cache,这是怎么实现的?

第二次Build:

sudo docker build -t getting-started .
Sending build context to Docker daemon  4.659MB
Step 1/7 : FROM node:12-alpine
 ---> deeae3752431
Step 2/7 : RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
 ---> Using cache
 ---> 306dcbb9fb1b
Step 3/7 : RUN apk add --no-cache python g++ make
 ---> Using cache
 ---> 30167547cd72
Step 4/7 : WORKDIR /app
 ---> Using cache
 ---> 9b4f5c2f993f
Step 5/7 : COPY . .
 ---> fb639ed69103
Step 6/7 : RUN ["yarn", "install", "--production"]
 ---> Running in 3bd8d313084f
yarn install v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.9: The platform "linux" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 61.84s.
Removing intermediate container 3bd8d313084f
 ---> 9435838e03d2
Step 7/7 : CMD node src/index.js
 ---> Running in 49c60121400a
Removing intermediate container 49c60121400a
 ---> 7cb5bd6154b2
Successfully built 7cb5bd6154b2
Successfully tagged getting-started:latest

Run

docker run -dp 3000:3000 getting-started

其中-d指令表示这个镜像放到后台去执行,此时不会进入到容器中,而是会直接detached,类似于screen的detached。

-p表示要指定端口的映射,后面3000:3000表示将宿主机的3000端口映射到容器的3000端口。经过试验,前面的是宿主机的端口号,后面的是容器的端口号。

最后,通过build时指定的tag名称来指定了需要运行的镜像的名字——getting-started。可见,build打包好的镜像应该是保存在了一个docker的仓库里的,我们可以通过构建时指定的标签来指定它。

访问3000端口即可看到这个程序已经启动起来了:

Docker学习笔记(一)

通过docker ps命令可以查看已经启动的容器,然后通过docker stop <CONTAINER ID>来关闭已经detached的容器。

更新源码

按照官方文档,将src/static/js/app.js文件第56行进行修改:

 -                <p className="text-center">No items yet! Add one above!</p>
 +                <p className="text-center">You have no todo items yet! Add one above!</p>

此时再执行build,查看3000端口,发现更改没有生效。再执行run,获得如下报错:

docker: Error response from daemon: driver failed programming external connectivity on endpoint hardcore_colden (b2a0229cf98dca6b7a0f58a654b8263ee6e4718629116598128d1a2c633b733e): Bind for 0.0.0.0:3000 failed: port is already allocated.

报错显示宿主机的3000端口已经被占用,改用3001:

docker run -dp 3001:3000 getting-started

可以看到更改生效了,而此时访问3000仍旧可以看到旧的未更改的app。

Docker学习笔记(一)

此时,我们有以下这些命令:

构建一个镜像:

docker build -t <NAME>

运行一个镜像:

docker run -dp <HOST_PORT>:<CONTAINER_PORT> <NAME>

停止一个容器(但这个容器仍旧存在):

docker stop <CONTAINER>

删除一个容器(必须已停止):

docker rm <CONTAINER>

停止并删除一个容器:

docker rm -r <CONTAINER>

查看正在运行的:

docker ps

查看所有容器(包括已停止但未删除的):

docker ps -a

指定<CONTAINER>的方式有三种:

  1. CONTAINER ID的全称;
  2. CONTAINER ID的前缀子串,长度只要能够区分不同的容器即可;
  3. NAMES,即容器的名字;

目前看来我已经启动的容器都有一个自然语言的NAME,但是目前不知道这个是怎么指定的,也不知道怎么生成的。

分享/发布镜像

首先,注册一个DockerHub账号,登录并创建一个新的仓库:

Docker学习笔记(一)

然后通过

docker login

来登录自己的DockerHub账号。在通过

docker tag getting-started <USER_ID>/getting-started

来创建一个应该与DockerHub对应的镜像,这个镜像是getting-started的复制。再通过

docker push <USER_ID>/getting-started

来将本地的镜像同步至DockerHub仓库。然后在另一台机器上(我在阿里云的自己的服务器上)登录后执行

docker pull <USER_ID>/getting-started

来将远程仓库的镜像同步下来。这一系列操作与git差不多,只不过git可以通过git push -u来创建本地与远程仓库的关联,而docker通过docker tag来创建一个镜像的复制表示对远程仓库的引用。

最后在服务器上执行run,来启动容器运行同步下来的进项。并且通过ssh的端口转发实现访问。

Docker学习笔记(一)

这里我将从远程仓库同步下来的镜像启动在3001端口,将未修改源码时build的镜像启动在3000端口。

通过docker images可以查看目前所有的镜像。这里可以看到每个镜像都有一个TAG,默认值为latest,我们可以通过在镜像名后面加:<tagname>(注意冒号)来指定TAG。

小结

至此,我们简单尝试了docker的镜像构建、运行,以及远程仓库的同步、拉取的基本操作。

上一篇:将map中的值赋值给一个java对象


下一篇:安装gitbook卡在installing GitBook 3.2.3的解决方法