zoukankan      html  css  js  c++  java
  • Docker学习笔记(一)

    Docker学习笔记(一)

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

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

    2021/7/5

    主要内容为跟随官方文档的Get-Start过一遍流程,加上一些自己的探索和思考。

    参考资料

    什么是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 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 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 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的端口转发实现访问。

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

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

    小结

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

    下一篇:Docker学习笔记(二):https://www.cnblogs.com/SnowPhoenix/p/14979696.html

  • 相关阅读:
    nginx安装http2.0协议
    nginx内置变量 大全
    nginx全局查看进程
    Nginx1.14.2新增tcp/udp代理stream
    Nginx Location指令配置及常用全局变量
    Nginx配置udp/tcp代理
    ps -ef|grep详解
    centos7安装nginx(基础篇)
    js转义字符
    redis win连接以及配置连接密码
  • 原文地址:https://www.cnblogs.com/SnowPhoenix/p/14974321.html
Copyright © 2011-2022 走看看