docker容器调试利器nicolaka/netshoot

背景

在日常工作中,我们一般会将容器进行精简,将其大小压缩到最小,以此来提高容器部署效率,参考小米云技术 – Docker最佳实践:5个方法精简镜像。但有一个比较尴尬的问题就是对容器排障,由于容器里没有了我们日常工作中用到许多排障命令,如top、ps、netstat等,所以想排除故障的话,常用的做法是安装对应的命令,如果容器过多的话,再这样搞就有些麻烦了,特别是在一些安装包源速度很慢的情况。

解决方案

今天发现一篇文章(简化 Pod 故障诊断:kubectl-debug 介绍)介绍针对此类问题的解决方案的,这里介绍的是一个叫做 kubectl-debug 的命令,主要由国内知名的PingCAP公司出品的,主要是用在k8s环境中的。我们知道容器里主要两大技术,一个是用cgroup来实现容器资源的限制,一个是用Namespace来实现容器的资源隔离的)。(kubectl-debug 命令是基于一个工具包(https://github.com/nicolaka/netshoot) 来实现的,其原理是利用将一个工具包容器添加到目标容器所在的Pod里,实现和目标容器的Namespace一致,从而达到对新容器进程可见性,这样我们就可以直接在目标容器里操作这些命令。所以在平时开发环境中,可以很方便的直接使用这个工具包来实现对容器的排障。

netshoot包含一组强大的工具,如图所示

工具包清单

apache2-utils
bash
bind-tools
bird
bridge-utils
busybox-extras
calicoctl
conntrack-tools
ctop
curl
dhcping
drill
ethtool
file
fping
iftop
iperf
iproute2
iptables
iptraf-ng
iputils
ipvsadm
libc6-compat
liboping
mtr
net-snmp-tools
netcat-openbsd
netgen
nftables
ngrep
nmap
nmap-nping
openssl
py-crypto
py2-virtualenv
python2
scapy
socat
strace
tcpdump
tcptraceroute
util-linux
vim

看了上图不得不说几乎包含了所有的调度命令。

使用也很方便,只需要一条命令即可

$ docker run -it --net container:<container_name> nicolaka/netshoot

参数 --net--network 的缩写,有四种值,用法参考:https://docs.docker.com/engine/reference/run/#network-settings

执行命令后,会自动创建一个镜像为 nicolaka/netshoot 的容器,并共享一个网络栈,即Namespace 和目标容器的一样,并自动进入到目标容器 里。如果执行命令 hostname 命令的话,结果显示的是目标 “容器ID” 。此时就可以在容器里执行常用的一些ps、 top、netstat、iftop 之类的命令。

➜  ~ docker run -it --rm --network container:mysql80 nicolaka/netshoot
                    dP            dP                           dP
                    88            88                           88
88d888b. .d8888b. d8888P .d8888b. 88d888b. .d8888b. .d8888b. d8888P
88'  `88 88ooood8   88   Y8ooooo. 88'  `88 88'  `88 88'  `88   88
88    88 88.  ...   88         88 88    88 88.  .88 88.  .88   88
dP    dP `88888P'   dP   `88888P' dP    dP `88888P' `88888P'   dP

Welcome to Netshoot! (github.com/nicolaka/netshoot)
root @ /
 [1] 🐳  → ls
bin    dev    etc    home   lib    lib64  media  mnt    opt    proc   root   run    sbin   srv    sys    tmp    usr    var

root @ /
 [2] 🐳  → hostname
7ff422d3f75d

root @ /
 [3] 🐳  → ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/bash -l
   15 root      0:00 ps

root @ /
 [4] 🐳  → netstat -an | grep LISTEN
tcp        0      0 :::33060                :::*                    LISTEN
tcp        0      0 :::3306                 :::*                    LISTEN
unix  2      [ ACC ]     STREAM     LISTENING      18007 /var/run/mysqld/mysqld.sock
unix  2      [ ACC ]     STREAM     LISTENING      19694 /var/run/mysqld/mysqlx.sock

root @ /
 [5] 🐳  →

上面我们添加了--rm 参数,主要是为了使用完毕后,及时清除临时容器相关的资源。这里我们将临时容器的Namespace和mysql80 容器相同

mysql80容器的ID为 7ff422d3f75d,即hostname 命令的输出结果。

现在我们先不要从这个容器里退出,再开启一个新终端进入到临时容器(bb47226c955e)里

➜  ~ docker exec -it bb4 /bin/bash
bash-5.0# hostname
7ff422d3f75d
bash-5.0# ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/bash -l
   22 root      0:00 /bin/bash
   28 root      0:00 ps
bash-5.0# netstat -an | grep LISTEN
tcp        0      0 :::33060                :::*                    LISTEN
tcp        0      0 :::3306                 :::*                    LISTEN
unix  2      [ ACC ]     STREAM     LISTENING      18007 /var/run/mysqld/mysqld.sock
unix  2      [ ACC ]     STREAM     LISTENING      19694 /var/run/mysqld/mysqlx.sock
bash-5.0#

从hostname和进程ID的结果来看,进入的还是msyql80容器。

工具包容器的作用就像将里面的内容合并到了目标容器里一样。使用到了--net 参数。

最后执行 exit 或者 logout 命令退出容器,此时临时容器及其volume将自动被删除(参数–rm)。

总结

这里主要考察了容器的Namespace隔离性的原理,通过将不同容器的Namespace和其它容器的Namespace保持一致,从而实现了对进程的可见性。

问题延伸

这里抛出另一个问题,如果两个容器都存在一个一模一样的进程,这个时候会出错吗?如果不会的话,为什么(应用存储路径不一样?或者进程PID不一样)?我们可见的是哪个容器的里程,临时容器还是目标容器呢?

参考:
https://blog.csdn.net/xbw_linux123/article/details/81873490

docker容器 Exited (137)错误代码

最近要搭建es集群,由于刚接触es不久,直接使用的docker构建,发现当用两个容器搭建好集群时,再添加新的es容器节点时,总是出现其它容器被kill的现象,查看容器日志未发现任何错误信息,一段时间段非常的迷茫。

由于对es也是刚刚接触,起始认为是配置不当引起了,于是一直在配置这一块找问题,网上的有些教程是直接在物理机器上或者虚拟机上进行部署,而自己的环境是docker, 通过 docker-compose 来部署的,环境有些差异。

有网友提醒有可能是由 OOM 引起的问题,因为代码是137, 使用命令 “docker inspect 容器ID” 查看了一下容器, status列显示”OOMKilled”: false” ,所以从这里查看的话,并非是 OOM 引起的,再加上首次遇到这个问题的时候,个人分析容器的启动过程,新启动一个容器时,dockerd应该先检查内存是否足够,如果不足够的话,则启动新窗口失败才对,而不是将已存在的窗口killed,再启动新容器,所以仍将OOM的原因排除掉了。

后来发现一篇文章https://www.petefreitag.com/item/848.cfm 介绍到这个和 docker for mac 分配的内存大小有关系,试着将给 docker 分配的内存调整为4G,重新docker-compose up 竟然解决了。

由此可见docker软件对容器启动时处理逻辑与自己分析的还是有些差异的。 可惜浪费了好几天的时间,很值的好好反思一下!

总结一下,还是基础不牢固,解决思路,分析问题不清晰,一直在配置这一块的周旋,虽然首次怀疑是OOM引起的,但在容器日志里并没有OOM的日志信息,再加上集群编排时有“ – “ES_JAVA_OPTS=-Xms512m -Xmx512m” 配置项,给es分配512M的内存大小,也忽略掉物理机器给docker分配内存大小这个因素了,所以直接将OOM的原因排除了,而未想到OOM发生时,并不保证一定在容器日志里记录的。

docker中将MySQL运行在容器中失败提示“ InnoDB : Error 22 with aio_write”的解决办法

今天利用docker容器创建mysql8.0的时候(window系统),指定了本地宿主机器的一个目录为容器mysql的datadir目录,发现创建失败了。

创建命令:

$ docker run -d --name mysql81 -v /e/container/mysql/mysql81/datadir:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -p 33081:3306 mysql

错误提示:

$ docker logs mysql81
2019-01-26T03:05:42.567230Z 0 [Warning] [MY-011070] [Server] 'Disabling symbolic links using --skip-symbolic-links (or equivalent) is the default. Consider not using this option as it' is deprecated and will be removed in a future release.
2019-01-26T03:05:42.567618Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.13) starting as process 1
2019-01-26T03:05:42.572006Z 0 [Warning] [MY-010159] [Server] Setting lower_case_table_names=2 because file system for /var/lib/mysql/ is case insensitive
2019-01-26T03:05:42.832344Z 1 [ERROR] [MY-012592] [InnoDB] Operating system error number 22 in a file operation.
2019-01-26T03:05:42.832473Z 1 [ERROR] [MY-012596] [InnoDB] Error number 22 means 'Invalid argument'
2019-01-26T03:05:42.832556Z 1 [ERROR] [MY-012646] [InnoDB] File ./#innodb_temp/temp_1.ibt: 'aio write' returned OS error 122. Cannot continue operation
2019-01-26T03:05:42.832609Z 1 [ERROR] [MY-012981] [InnoDB] Cannot continue operation.

解决办法:

docker run -u 1000:50 -d --name mysql81 -v /e/container/mysql/mysql81/datadir:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -p 33081:3306 mysql --innodb-use-native-aio=0

参数:

-u 表示运行实例的用户,这里的 1000:50 表示的是docker这个用户
–innodb-use-native-aio=0 为MySQL的参数,作用是启用异步操作功能,提高MySQL性能

注意:
上面使用了docker运行了mysql实例,则如果进入到容器内部进行一些其它需要高级权限的操作的话,如apt update则会提示权限不足的情况。

参考:https://github.com/docker-library/mysql/issues/371

docker build . 命令后面的.是什么意思

今天来公司自己构建了一个Dockerfile,放在一个经常用到的项目目录里,内容如下:

# This is a comment
FROM ubuntu:14.04
MAINTAINER Docker Newbee <newbee@docker.com>
RUN apt-get -qq update
RUN apt-get -qqy install ruby ruby-dev
RUN gem install sinatra

然后执行

sudo docker build -t "cfanbo/test:v2" .

发现在构建的时候发送给 docker daemon 竟然有4G多,超极大。首先的第一反映出问题了。一个ubuntu镜像也没有这么大呀,况且现在还没有开始从远程pull 镜像呢。

那到底什么情况了呢?经过一翻搜索,发现在docker build . 的时候,会将当前目录里的内容发送给 docker daemon。只需要加一个 .dockerignore 文件,将其它内容排除掉就可以了,类似于git中的.gitignore文件的作用。

后面就想通过docker build -h 命令查看一下相关的参数,发现对于Dockerfile 文件位置需要 -f 参数指定,并非上面命令后面的.符号。那就有些疑惑了,原来学习的时候一直把.当作当前目录指定的。难道.有其它的作用,后面查看了一些文档,发现一直对 docker build 命令的过程不太了解。通过查找了一些资料才发现它的真正作用。

Docker 在运行时分为 Docker引擎(服务端守护进程) 以及 客户端工具,我们日常使用各种 docker 命令,其实就是在使用客户端工具与 Docker 引擎 进行交互。

那么当我们使用 docker build 命令来构建镜像时,这个构建过程其实是在 Docker引擎 中完成的,而不是在本机环境。

那么如果在 Dockerfile 中使用了一些 COPY 等指令来操作文件,如何让 Docker引擎 获取到这些文件呢?

这里就有了一个镜像构建上下文的概念,当构建的时候,由用户指定构建镜像的上下文路径,而 docker build 会将这个路径下所有的文件都打包上传给 Docker 引擎,引擎内将这些内容展开后,就能获取到所有指定上下文中的文件了(参考下方docker架构图)。

比如说 dockerfile 中的 COPY ./package.json /project,其实拷贝的并不是本机目录下的 package.json 文件,而是 docker引擎中展开的构建上下文中的文件,所以如果拷贝的文件超出了构建上下文的范围,Docker引擎是找不到那些文件的。


Docker构架

所以 docker build . 最后的 . 号,其实是在指定镜像构建过程中的上下文环境的目录。

理解了上面的这些概念,就更方便的去理解 .dockerignore 文件的作用了。

为验证.的作用,我们可以创建一个空的目录,在目录里执行

docker build -f /home/tom/Dockerfile . 

会发现命令完全正常执行。

windows下更新docker源(aliyun)

每个aliyun账号都有一个专属的镜像源 https://cr.console.aliyun.com/cn-hangzhou/mirrors

我这里安装的是 Docker Toolbox 软件,更新docker源有两种情况,一种是你还没有创建过Docker Machine,另一种是你已经创建过了Docker Machine。

一、未安装过

创建一台安装有Docker环境的Linux虚拟机,指定机器名称为default,同时配置Docker加速器地址。

$ docker-machine create –engine-registry-mirror=https://xxxx.mirror.aliyuncs.com -d virtualbox default

查看机器的环境配置,并配置到本地。然后通过Docker客户端访问Docker服务。

$ docker-machine env default
$ eval “$(docker-machine env default)”
$ docker info

这里 xxxx 是您的专有加速器地址


二、已安装过

登录已创建的Docker VM
$ docker-machine ssh default
$ sudo vi /var/lib/boot2docker/profile

在EXTRA_ARGS中添加

–registry-mirror=https://xxxx.mirror.aliyuncs.com

EXTRA_ARGS='
--label provider=virtualbox
--registry-mirror=https://fgxrbz510.mirror.aliyuncs.com
'
CACERT=/var/lib/boot2docker/ca.pem
DOCKER_HOST='-H tcp://0.0.0.0:2376'
DOCKER_STORAGE=aufs
DOCKER_TLS=auto
SERVERKEY=/var/lib/boot2docker/server-key.pem
SERVERCERT=/var/lib/boot2docker/server.pem

export "NO_PROXY=192.168.99.100"

重启Docker服务

$ sudo /etc/init.d/docker restart

有时候会提示以下错误,可以忽略。如果在连接终端执行 docker info 提示服务未启动的话,可以尝试将虚拟机手动重启一下基本可以解决,我这里用的是virtualbox

Stopping dockerd (3590)
warning: ‘aufs’ is not a supported storage driver for this boot2docker install — ignoring request!

Stopping dockerd (3590)
warning: ‘aufs’ is not a supported storage driver for this boot2docker install — ignoring request!
see https://github.com/boot2docker/boot2docker/issues/1326 for more details
Starting dockerd

另外对于windows上使用Docker Toolbox (VirtualBox)的用户需要注意,当创建一个容器的时候,并指定了端口映射的时候,在宿主本机没有办法通过端口访问容器实例的。因为参数 -p 33065:3306 中的33065指的是VirtualBox中的端口,而非当前宿主机器的端口,此时如果用netstat查看的话,是看不到33065端口的。想访问容器,需要在宿主机器与VirtualBox之间再映射一个端口 33065:33065

至于原因吗,很好理解的,docker运行需要使用Linux内核,于是windows使用VirtualBox 搞了一个linux 虚拟机器,所以对于docer run 命令指定端口的时候,其实指定的是 linux服务器与容器的端口映射关系。

使用Dockerfile构建Swoole+php7环境

FROM php:7.2.7-cli

RUN apt-get update \
    && apt-get install -y libmemcached-dev zlib1g-dev
    
RUN pecl install redis-4.0.1 \
    && pecl install swoole-4.0.1 \
    && pecl install memcached-3.0.4 \
    && pecl install xdebug-2.6.0 \
    && docker-php-ext-enable redis swoole memcached xdebug
    
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
CMD [ "php", "-m" ]

构建完环境后,使用方法见:https://blog.haohtml.com/archives/17925

推荐文章:Dockerfile 最佳实践

一个docker-compose微服务脚本,自用

容器为swoole+php7

docker-compose.yml

version: '3.6'
services:
  redis:
    image: redis
  web:
    image: cfanbo/swoole4_php7:v1
    depends_on:
      - redis
    links:
      - redis
    volumes:
      - /Users/sxf/sites/msgserve:/usr/src/myapp
    command: "php /usr/src/myapp/src/wx_push_server.php start"

对于 wx_push_server.php文件里redis的主机地址应该写成docker-compose配置文件里的容器服务名(redis)