呓语 | 杨英明的个人博客

专注于c++、Python,欢迎交流

By

Docker 入门指北(踩坑心得)

前言

自从上次在一台陌生服务器上部署一个比较简单的 web 服务花了半个多小时之后,我觉得是时候学一下这个闻名已久的大鲸鱼(Docker)了。

注:以下 zzzzzzzz 代表镜像ID,xxxxxxxx 代表容器ID

安装 Docker (in ubuntu)

# 安装 docker 自己维护的版本
# 官方提供有一键安装脚本
sudo apt-get install curl
curl -sSL https://get.docker.com | sudo sh
# 或者
wget -qO- https://get.docker.com/ | sh

安装完成后有个提示,将你的用户加入docker用户组就不用每次加上sudo运行docker命令了:

sudo usermod -aG docker your-user

启动/关闭/重启Docker服务

sudo service docker start
sudo service docker stop
sudo service docker restart

启动一个容器

# docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
# [OPTIONS]
# -i  保持标准输入,常同-t一起使用来申请一个控制台进行数据交互
# -t  为容器重新分配一个伪输入终端(tty),通常与 -i 同时使用
# [COMMAND] 启动容器后运行的命令(仅一条命令,运行完就关闭容器)
docker run -it ubuntu /bin/bash

# 退出容器,让容器在后台运行
Ctrl+P & Ctrl+Q

# 重新进入容器
docker attach xxxxxxxx
# 或者
docker exec -it xxxxxxxx /bin/bash

启动一个守护式容器

(守护式容器可以在后台作为守护程序一直运行)

比如说启动一个运行 uwsgi 程序的守护式容器

# docker run -d IMAGE [COMMAND] [ARG...]
docker run -d 173a38dfaf07 uwsgi --ini uwsgi.ini --module douyin_back.wsgi

这个容器会作为守护式容器一直运行,用 docker ps 可以查看到它:

> docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
dda9199f2fdf        173a38dfaf07        "uwsgi --ini uwsgi.i…"   9 minutes ago       Up 9 minutes                            xenodochial_margulis

使用 docker logs 可以查看该容器的输出:

# docker logs [-f] [t] [--tail] 容器名
#   -f --follows=true | false       是否跟踪输出,默认为 false
#   -t --timestamps=true | false    是否带时间戳,默认为 false
#   --tail="all"    显示最后多少条输出,默认为 all
docker logs -tf dda9199f2fdf

使用 Dockerfile 构建容器的经典过程

# 构建镜像
docker build -t MYROPO/MYNAME:MYTAG .

# 显示构建好的镜像
docker image ls

# 运行构建好的镜像,生成容器
docker run --name CONTAINER_NAME -p 8004:80 -d IMAGE_ID
# --name 表示给生成的容器起名字
# -p port1:port2 表示宿主机和容器的端口映射
#    映射成功后宿主机会使用 port1 来连接容器中提供的服务
#    在容器中程序使用 port2 端口开放服务
# -d 表示守护式启动容器
# 其它参数:
# --link=other_container_name:the_container_alias
#    表示该容器连接了另一个容器 other_container_name,
#    并给它起了别名 the_container_alias,
#    在该容器中可以使用这个别名 the_container_alias 使用它提供的服务

# 查看运行中的容器
docker ps

# 查看容器的运行日志
docker logs -tf xxxxxxxx

# 进入容器查看
docker exec -it xxxxxxxx /bin/bash

使用 Nginx 反向代理到容器中的 web 应用

我们创建好一个包含 web 应用的容器之后,往往会映射一个宿主机端口到容器端口来使用这个 web 应用提供的服务,比如:

docker run -p 8070:80 -d myapp   # 使用宿主机 8070 端口映射到容器的 80 端口

这样就可以在宿主机中使用 http://localhost:8070 访问应用提供的服务,就像没有使用容器时一样,因为访问服务的方式和之前完全一样。

这个时候假设我们有一个域名 test.com ,现在想要把使用这个域名的二级域名 app.test.com 指向到这个 web 服务,就像这样:

http://app.test.com => http://localhost:8070

要做到这一步就需要 nginx 服务器闪亮登场,我们需要用它来做 反向代理

添加宿主机 nginx 的配置文件 /etc/nginx/conf.d/app.test.com.conf

server {
    listen 80;
    server_name app.test.com;   # 指定 nginx 需要代理的域名
    location / {
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # 这一步是关键,指定域名转发到的内部服务
        proxy_pass http://localhost:8070;   
    }
}

如果 web 应用由 uWSGI 提供通信,Nginx 配置文件:

server {
    listen 8005;
    server_name app.test.com;

    # charset
    charset utf-8;

    # max upload size
    client_max_body_size 75M;

    location / {
        include uwsgi_params;
        # 这一步是关键,指定域名转发到的内部 uWSGI 服务
        uwsgi_pass 127.0.0.1:8004;
        uwsgi_read_timeout 120s;
    }
}

配置完之后重启 nginx 服务器

sudo service nginx restart

参考:Docker部署(五):Nginx and 反向代理配置

查看 Docker 容器日志文件

其实使用 Docker 运行应用的过程我有个不习惯的地方,之前运行应用都会随时保存日志到本地,而 Docker 支持用 docker logs 命令查看。

所以在使用更高级的日志收集功能前,我研究了下容器日志文件存放的位置,默认位置:

/var/lib/docker/containers/[container_id]/

日志文件以 json.log 结尾,里面记录了容器标准输出 stdout 的内容。

这个目录默认必须用 root 用户才能访问。我是腾讯云用户,默认没有 root 账号,解决方案:

  1. 先用ubuntu账号登录,执行sudo passwd root
  2. 按要求输入密码,请牢记。
  3. 执行sudo vi /etc/ssh/sshd_config
  4. 找到PermitRootLogin without-password这一行,把后面的without-password改为yes,保存文件。
  5. 执行sudo service ssh restart

参考:Docker容器日志查看与清理(亲测有效) & 腾讯云主机如何使用root账号登录,不能使用root登录怎么办

Tips

Dockerfile 的 COPY 命令可以自动创建文件夹

Docker 的 COPY 命令可以自动创建文件夹,比如你想复制当前目录所有文件到镜像中:

COPY . /root/xxx/

其中 xxx 目录就算没有,COPY 命令也会自动帮你创建,然后再复制当前文件夹内容进去。这样其实就不用我们手动 RUN mkdir /root/xxx 了。

Dockerfile 中的 EXPOSE 有什么用?

Dockerfile 中的 EXPOSE 可以声明容器暴露的接口,以供宿主机在外部使用容器提供的服务。但是这个命令的功能也可以在 docker run 时,用 -p 参数覆盖,比如 docker run -p 8070:80 mywebapp。这条命令可以将宿主机的 8070 端口映射到容器的 80 端口,即利用宿主机的 8070 端口使用容器中的 web 应用在 80 端口提供的服务。

既然 EXPOSE 的功能可以被 run -p 方式覆盖,那么它有什么用呢?

在一篇博文中找到了解答:

Dockerfile 的作者一般在包含 EXPOSE 命令时都只将其作为哪个端口提供哪个服务的提示

参考:Docker网络原则入门:EXPOSE,-p,-P,-link

Dockerfile 可以有多个 FROM 吗?

参考:Docker中Dockerfile多From 指令存在的意义

使用 node 和 nginx 镜像部署 vue 应用的 Dockerfile 实例 (双FROM):Dockerize Vue.js App - Vue.js

常用 Docker 命令

查看镜像/容器/所有容器

docker image ls
docker container ls
docker container ls --all

启动并进入容器

docker container run -it xxxxxxxx

删除指定镜像/容器

docker image rm zzzzzzzz
docker container rm xxxxxxxx

删除所有孤立的镜像/容器

# 删除所有孤立的镜像/容器
docker image prune
docker container prune

# 强行删除所有不使用的镜像
docker image prune --force --all
docker image prune -f -a

拷贝文件

# 容器 => 宿主机
docker cp xxxxxxxx:/path/to/file path/to/file
# 宿主机 => 容器
docker cp path/to/file xxxxxxxx:/path/to/file

容器启动/暂停/关闭方法

# 启动一个新容器,退出容器后会终止
# 不管容器存不存在,都会创建一个新容器
docker run zzzzzzzz

# 启动一个已经终止的容器
docker start xxxxxxxx

# 终止一个运行中的容器
docker stop xxxxxxxx

# 强行关闭一个运行中的容器
# 没有 stop 安全,可能会丢失上下文数据
docker kill xxxxxxxx

# 重启一个容器
docker restart xxxxxxxx

查看某镜像的构建过程

docker history zzzzzzzz

性能评估

一个基于 uwsgi+Django 的 web 服务的内存对比(仅供参考)

  • 原版本占用内存:0.03G (1.02G => 1.05G)
  • Docker 化之后占用内存:0.03G (1.02G => 1.05G)

把原来的服务 Docker 化之后消耗的内存竟然差不多,有些超出我的预计,原本我最担心的是 Docker 十分消耗内存。

遇到的坑

docker 中 python 使用中文报 "docker 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128) docker" 的错误

解决方案(修改Dockerfile):

FROM ubuntu:15.10

RUN apt-get -qq update && \
    apt-get -q -y upgrade && \
    apt-get install -y sudo curl wget locales && \
    rm -rf /var/lib/apt/lists/*

# Ensure that we always use UTF-8 and with Canadian English locale
RUN locale-gen en_CA.UTF-8

COPY ./default_locale /etc/default/locale
RUN chmod 0755 /etc/default/locale

ENV LC_ALL=en_CA.UTF-8
ENV LANG=en_CA.UTF-8
ENV LANGUAGE=en_CA.UTF-8

参考: Docker Python set utf-8 locale & docker ubuntu /bin/sh: 1: locale-gen: not found

参考资料

  • Docker 入门教程 - 阮一峰
    http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html

  • 只要一小时,零基础入门Docker - 知乎
    https://zhuanlan.zhihu.com/p/23599229

  • Docker 微服务教程 - 阮一峰(有点复杂)
    http://www.ruanyifeng.com/blog/2018/02/docker-wordpress-tutorial.html

  • Docker下的ubuntu 安装python3.6 及pip3
    https://www.jianshu.com/p/2a5cd519e583

  • docker运行python3.6+flask小记(一个比较好的 Dockerfile 文件示例)
    https://www.cnblogs.com/xuanmanstein/p/7630606.html

  • Docker镜像推送(push)到Docker Hub
    https://blog.csdn.net/boonya/article/details/74906927

  • Docker容器的创建、启动、和停止
    https://www.cnblogs.com/linjiqin/p/8608975.html

  • Minimal Python deployment on Docker with uWSGI
    https://medium.com/@greut/minimal-python-deployment-on-docker-with-uwsgi-bc5aa89b3d35

  • Docker入门,如何部署Django uwsgi nginx应用(阿里云 容器仓库的使用)
    https://blog.csdn.net/sence_yxy/article/details/79216120

原创声明

转载请注明:呓语 » Docker 入门指北(踩坑心得)