我想将ChefDK安装在Docker容器中,以便将其所有依赖项与主机上的所有Ruby安装隔离开来,并允许我轻松地并行运行不同版本以进行测试升级。
所以我从一个基本的Dockerfile开始来安装ChefDK…
from ubuntu:16.04
RUN apt-get update
RUN apt-get install -y curl
RUN curl -O https://packages.chef.io/files/stable/chefdk/2.4.17/ubuntu/16.04/chefdk_2.4.17-1_amd64.deb
RUN dpkg -i chefdk_2.4.17-1_amd64.deb
如果我们构建它,然后在其中放入bash外壳……
docker build -t chefdk .
docker run -it chefdk bash
我们已经安装了厨师和刀!
root@6bb552c50752:/# which chef
/usr/bin/chef
root@6bb552c50752:/# which knife
/usr/bin/knife
太好了,现在我需要将食谱安装在容器中,以便可以运行测试厨房,将它们安装在/cookbooks
docker run -it -v /Users/aaronkalair/cookbooks:/cookbooks chefdk
那行得通,但是因为我们没有在Dockerfile中指定用户,所以我们是root用户,因此每个新文件也由容器内部的root用户拥有。
root@d374bab13add:/cookbooks/teabot# touch test.txt
root@d374bab13add:/cookbooks/teabot# ls -lah test.txt
-rw-r--r-- 1 root root 0 Dec 23 15:28 test.txt
为了避免出现任何问题,让我们在容器内创建一个与主机上的用户具有相同UID的用户...
在主机上,我的名称是UID 501,这在我对其进行测试的3台计算机上都是一致的,因此Mac似乎将501分配给了创建的第一个用户帐户。
Aarons-iMac:chefdk aaronkalair$ id
uid=501(aaronkalair) ...
因此,我们可以通过将其添加到Dockerfile的末尾来使用uid 501
在容器内创建一个名为aaronkalair
的用户
RUN useradd -u 501 aaronkalair
RUN mkdir -p /home/aaronkalair
RUN chown aaronkalair:aaronkalair /home/aaronkalair
USER aaronkalair
现在让我们测试一下如何为我的Teabot食谱创建一个测试厨房。 首先,我们需要安装gem
root@6c602759d146:/cookbooks/teabot# bundle install
bash: bundle: command not found
嗯,我们没有安装Ruby或其任何关联的工具,但是我们不需要这样做,因为Chef附带了它们的嵌入式版本。
root@6c602759d146:/cookbooks/teabot# ls /opt/chefdk/embedded/bin/
... <124 packages including, bundle, gem, ruby etc... >
因此,通过将其添加到我们的Dockerfile中,将其放到我们的路径中
ENV PATH="/opt/chefdk/embedded/bin:${PATH}"
现在让我们再次尝试bundle install
。
当我在不同的食谱中使用不同版本的Gems时,只需将它们安装到此食谱中的文件夹中,而不必根据我上次使用的食谱在全球范围内安装不同版本的杂烩
bundle install --path vendor --binstubs
不幸的是,这激起了关于extconf失败的错误
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
current directory: /cookbooks/teabot/vendor/ruby/2.4.0/gems/libyajl2-1.2.0/ext/libyajl2
/opt/chefdk/embedded/bin/ruby -r ./siteconf20171223-8-1u0q211.rb extconf.rb
creating Makefile
/cookbooks/teabot/vendor/ruby/2.4.0/gems/libyajl2-1.2.0/ext/libyajl2
extconf.rb:104:in `makemakefiles': unhandled exception
from extconf.rb:138:in `<main>'
extconf failed, exit code 1
原生扩展,Makefiles和extconf听起来像我们缺少一些依赖来安装C依赖关系,让我们抓住了build-essentials
,看看会发生什么,并且在我们编辑Dockerfile时,让bundle命令成为别名。
RUN apt-get install -y curl build-essential
RUN echo "alias bundle-install='/opt/chefdk/embedded/bin/bundle install --path vendor --binstubs'" >> /home/aaronkalair/.bashrc
我们去那工作了
aaronkalair@3ac49ab61513:/cookbooks/teabot$ bundle-install
....
Bundle complete! 3 Gemfile dependencies, 104 gems now installed.
Bundled gems are installed into ./vendor.
现在我们需要容器中可用的Docker,以便Kitchen-docker可以制造容器。 最简单的方法似乎是将套接字从主机安装到容器中。
因此,我们将运行命令更改为:
docker run -it -v /Users/aaronkalair/cookbooks:/cookbooks -v /var/run/docker.sock:/var/run/docker.sock chefdk
并将Docker CLI安装在容器中
RUN apt-get install -y curl build-essential docker.io
这使我们可以访问容器内的Docker CLI,但似乎无法与主机上的Deamon进行通信
aaronkalair@4280bf11224b:/$ docker ps
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.26/containers/json : dial unix /var/run/docker.sock: connect: permission denied
查看其权限揭示了为什么
aaronkalair@4280bf11224b:/$ ls -lah /var/run/docker.sock
srw-rw---- 1 root staff 0 Dec 22 11:32 /var/run/docker.sock
只有root
用户或staff
组中的用户才能从套接字读取和写入数据,我将通过将aaronkalair
用户添加到staff组来解决此问题。 但是请务必谨慎,因为可以访问Docker套接字的任何人都可以访问主机。
RUN usermod -a -G staff aaronkalair
现在我们可以谈谈Docker守护进程
aaronkalair@43db476d04f7:/$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
43db476d04f7 chefdk "/bin/bash" 2 seconds ago Up 1 second upbeat_panini
因此,让我们尝试融合我们的测试厨房!
aaronkalair@43db476d04f7:/cookbooks/teabot$ ./bin/kitchen converge
....Lots of Good Things...
Failed to complete #create action: [Cannot assign requested address - connect(2) for [::1]:32776] on default-ubuntu-1604
奇怪的是, kitchen list
只说Errno::EADDRNOTAVAIL
并没有多大帮助
我是否真的很倒霉而其他人已经将32776用于其外围端口? 让我们安装netstat
看看。
aaronkalair@43db476d04f7:/cookbooks/teabot$ apt-get install net
-tools
E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
糟糕,我们需要以root用户身份进入容器进行调试
docker run -it -u root -v /Users/aaronkalair/cookbooks:/cookbooks -v /var/run/docker.sock:/var/run/docker.sock chefdk
然后netstat
在端口50332
上仅显示一个连接
root@e075956d7a2b:/# netstat
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 e075956d7a2b:50332 keeton.canonical.c:http TIME_WAIT
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags Type State I-Node Path
有趣的是,我们正在Docker内部做一些奇怪的Docker事情,也许它是在主机上使用的?
Aarons-iMac:~ aaronkalair$ netstat | grep 32776
也没有在那里不使用。
我们可以在厨房外绑定到端口32776
吗?
root@e075956d7a2b:/# nc -l 32776
root@e075956d7a2b:/# nc -l 0.0.0.0 32776
是的,一切正常
我们可以curl
这个占用端口32776
神奇东西吗?
root@e075956d7a2b:/# curl -v localhost:32776
* Rebuilt URL to: localhost:32776/
* Trying 127.0.0.1...
* TCP_NODELAY set
* connect to 127.0.0.1 port 32776 failed: Connection refused
* Trying ::1...
* TCP_NODELAY set
* Immediate connect fail for ::1: Cannot assign requested address
* Trying ::1...
* TCP_NODELAY set
* Immediate connect fail for ::1: Cannot assign requested address
* Failed to connect to localhost port 32776: Connection refused
* Closing connection 0
curl: (7) Failed to connect to localhost port 32776: Connection refused
有趣的是, ::1:
给我们的错误与来自test-kitchen的错误相同,这是IPv6有点奇怪吗?
快速谷歌确认的IPv6只能在码头工人,如果你指定一个特殊的标志- https://docs.docker.com/engine/userguide/networking/default_network/ipv6/
--ipv6
好的,我实际上不需要IPv6,那么如何停止使用它呢? 好localhost
在/etc/hosts
定义为
root@e075956d7a2b:/# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 e075956d7a2b
因此,如果我仅对那些IPv6线路进行核对,也许会很好吗?
(旁注:显然,编辑/etc/gai.conf
可以在IPv4行为发生之前更改IPv6 — https://askubuntu.com/a/38468 )
root@e075956d7a2b:/# vi /etc/hosts
root@e075956d7a2b:/# cat /etc/hosts
127.0.0.1 localhost
172.17.0.2 e075956d7a2b
并且让我们尝试再次收敛(如果有帮助,请使用调试日志记录)
root@e075956d7a2b:/cookbooks/teabot# ./bin/kitchen converge -l debug
...
D [SSH] opening connection to kitchen@localhost<{:user_known_hosts_file=>"/dev/null", :port=>32776, :compression=>false, :compression_level=>0, :keepalive=>true, :keepalive_interval=>60, :timeout=>15, :keys_only=>true, :keys=>["/cookbooks/teabot/.kitchen/docker_id_rsa"], :auth_methods=>["publickey"], :verify_host_key=>false}>
$$$$$$ [SSH] connection failed, terminating (#<Errno::ECONNREFUSED: Connection refused - connect(2) for 127.0.0.1:32776>)
好吧,这确认了上面的问题是由于尝试在未为其配置的Docker环境中使用IPv6。
现在我们有了一个新问题,到端口32776
的SSH连接被拒绝。
实际上在本地端口上监听了什么吗? 回到我们的朋友netstat
root@e075956d7a2b:/cookbooks/teabot# netstat --listening
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
Active UNIX domain sockets (only servers)
Proto RefCnt Flags Type State I-Node Path
不。
测试厨房是否真的设法使容器运行SSH守护进程?
root@e075956d7a2b:/cookbooks/teabot# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9ca4e56ab0b4 2fe546b254ce "/usr/sbin/sshd -D..." 56 minutes ago Up About an hour 0.0.0.0:32776->22/tcp defaultubuntu1604-nologin-43db476d04f7-hds4zcup
当然看起来像这样,如果我们在那个sshd
容器中执行exec实际正在运行?
root@e075956d7a2b:/cookbooks/teabot# docker exec -it 9ca4e56ab0b4 bash
root@9ca4e56ab0b4:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 16:17 ? 00:00:00 /usr/sbin/sshd -D -o UseDNS=no -o UsePAM=no -o PasswordAuthentication=yes
root 6 0 0 17:15 pts/0 00:00:00 bash
root 16 6 0 17:15 pts/0 00:00:00 ps -ef
是的
再看上面的32776
docker ps
的输出,我们看到32776
来自哪里,它是容器中22映射到主机上的端口。
但是我们不在主机上,而是在Dockers自己的网络上的容器内。
好的,这就是我们的位置:
- kitchen-docker制作了一个可以在其中运行
chef
的容器,但它假定我们在主机上运行,并且sshd
侦听Docker映射到localhost
上的22
端口 - 我们位于网络上的Docker容器中,而不是主机Docker,因此无法使用端口映射
22 -> 32776
,无论它位于localhost上的端口是什么
幸运的是,所有这些问题都应该可以解决,我们与运行sshd
的容器位于同一网络上,因此我们应该能够通过其IP地址与IP进行通信。
root@e075956d7a2b:/cookbooks/teabot# docker inspect 9ca4e56ab0b4 | grep -i ipaddress
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.3",
"IPAddress": "172.17.0.3",
快速检查一下是否可以与我们交谈
In the container created by kitchen-docker
root@9ca4e56ab0b4:/# nc -l -p 5000
PING
^C
在容器中运行chefdk
root@e075956d7a2b:/cookbooks/teabot# telnet 172.17.0.3 5000
Trying 172.17.0.3...
Connected to 172.17.0.3.
Escape character is '^]'.
PING
非常好,可以工作,现在可以修改kitchen-docker
在此处设置端口和主机名-https: //github.com/test-kitchen/kitchen-docker/blob/master/lib/kitchen/driver/docker.rb#L124
因此,让我们快速将IP地址和端口22入侵其中以检查其是否有效
root@e075956d7a2b:/cookbooks/teabot# vi vendor/ruby/2.4.0/gems/kitchen-docker-2.6.0/lib/kitchen/driver/docker.rb
<some quick edits>
让我们看看是否可行:
root@e075956d7a2b:/cookbooks/teabot# ./bin/kitchen converge -l debug
...
D [SSH] kitchen@172.17.0.3<{:user_known_hosts_file=>"/dev/null", :port=>"22", :compression=>false, :compression_level=>0, :keepalive=>true, :keepalive_interval=>60, :timeout=>15, :keys_only=>true, :keys=>["/cookbooks/teabot/.kitchen/docker_id_rsa"], :auth_methods=>["publickey"], :verify_host_key=>false}> (sudo -E sh -c '
[SSH] Established
...
到这里,它起作用了!
我为kitchen-docker编写了此补丁,以在不进行IP地址硬编码的情况下完成上述操作-https: //github.com/test-kitchen/kitchen-docker/pull/283/files
(旁注:给定Docker ID的方式是获得Docker容器IP地址的一种非常简单的方法)
docker inspect --format '{{ .NetworkSettings.IPAddress }}' <container id>
因此,我们可以在Docker容器内运行ChefDK和kitchen-docker(对kitchen-docker进行了一些小的更改)。 为此,我将vim
安装在容器中,并将vim
, git
和chef
配置从主机安装到容器中,因此不必离开容器即可在编辑和测试之间进行切换。
(尽管食谱是从主机安装的,所以您可以在主机上进行编辑,然后切换到容器以测试是否需要)
您可以在此处查看最终的Dockerfile并在我的Github存储库上运行命令-https: //github.com/AaronKalair/docker_apps/tree/master/chefdk