加载中

When they start using Docker,  people often ask: “How do I get inside my containers?” and people will  tell them “Run an SSH server in your containers!” But, as you’ll  discover in this post, you don’t need to run a SSHd daemon to get inside  your containers. Well unless your container is an SSH server, of course!

It’s tempting to run the SSH server,  because it gives an easy way to “get inside” of the container. Virtually  everybody in our craft used SSH at least once in their life. Most of us  use it on a daily basis, and are familiar with public and private keys,  password-less logins, key agents, and even sometimes port forwarding  and other niceties. With that in mind, it’s not surprising that people  would advise you to run SSH within your container. But you should think  twice.

当开始使用Docker时,人们经常问:“我该如何进入容器?”,其他人会说“在你的容器里运行一个SSH服务器”。但是,从这篇博文中你将会了解到你根本不需要运行SSHd守护进程来进入你的容器。当然,除非你的容器就是一个SSH服务器。

运行SSH服务器是很想当然的,因为它提供了进入容器的简便方式。在我们公司基本上每个人都最少使用过一次SSH。我们中有很大一部分人每天都会使用它,并且他们很熟悉公钥与私钥,无密码登录,密钥代理,甚至有时会使用端口转发和其他不常用的功能。正因如此,人们建议你在容器中运行SSH并不奇怪。但你应该仔细考虑下。

Let’s say that you are building a Docker image for a Redis server or a Java webservice. I would like to ask you a few questions.

  • What do you need SSH for?Most likely, you want to do backups, check  logs, maybe restart the process, tweak the configuration, possibly debug  the server with gdb, strace, or similar tools. We will see how to do  those things without SSH.

  • How will you manage keys and passwords?Most likely, you will either  bake those into your image, or put them in a volume. Think about what  you should do when you want to update keys or passwords. If you bake  them into the image, you will need to rebuild your images, redeploy  them, and restart your containers. Not the end of the world, but not  very elegant neither. A much better solution is to put the credentials  in a volume, and manage that volume. It works, but has significant  drawbacks. You should make sure that the container does not have write  access to the volume; otherwise, it could corrupt the credentials  (preventing you from logging into the container!), which could be even  worse if those credentials are shared across multiple containers. If  only SSH could be elsewhere, that would be one less thing to worry  about, right?

  • How will you manage security upgrades?The SSH server is pretty safe,  but still, when a security issue arises, you will have to upgrade all the containers using SSH. That means rebuilding and restarting all of  them. That also means that even if you need a pretty innocuous  memcached service, you have to stay up-to-date with security advisories,  because the attack surface of your container is suddenly much bigger.  Again, if SSH could be elsewhere, that would be a nice separation of concerns, wouldn’t it?

  • Do you need to “just add the SSH server” to make it work?No. You also need to add a process manager; for instance Monit or Supervisor.  This is because Docker will watch one single process. If you need  multiple processes, you need to add one at the top-level to take care of  the others. In other words, you’re turning a lean and simple container  into something much more complicated. If your application stops (if it  exits cleanly or if it crashes), instead of getting that information  through Docker, you will have to get it from your process manager.

  • You are in charge of putting the app inside a container, but are you  also in charge of access policies and security compliance?In smaller  organizations, that doesn’t matter too much. But in larger groups, if  you are the person putting the app in a container, there is probably a  different person responsible for defining remote access policies. Your  company might have strict policies defining who can get access, how, and  what kind of audit trail is required. In that case, you definitely don’t want to put a SSH server in your container.

假设你正在假设一个Redis Server或Java Webservice的Docker镜像, 我会问你以下几个问题:

  • 你需要用SSH来做什么? 一般来说, 你想做备份, 检查日志, 或者重启进程, 调整配置, 还有可能用gdb, strace或其他类似的工具来debug服务器。那我们会看一下我们怎么不使用SSH来做这些事情。

  • 你怎么管理你的密钥和密码的?一般来说,你要么把它们写到你的镜像中,要么就把它们放在一个卷中。你想一下如果你要更新这些密钥或密码你会怎么做呢。如果你把它们写到镜像里了,你就需要重建镜像,重新部署它们,然后重启容器。这还好,不算是世界末日,但是这绝不是一个高大上的方法。把它们放到卷中,然后通过管理卷来管理它们倒是比前一种好得多。这种方法是可用的,可是却有严重的缺陷。你必须要确认容器没有这个卷的写权限;否则,容器有可能会破坏密钥(这让你之后就进不去容器了),如果你再用一个卷共享给多个容器的话,情况会变得更糟。如果不用SSH,我们不就少一个需要担心的事了吗?

  • 你如何管理安全升级呢?SSH服务器是挺安全的,但是仍然会有安全问题,你会在必要的时候不得不升级所有使用SSH的容器。这意味着大量的重建和重启。也就是说,及时你有一个简单小巧的memcached服务,你还是不得不确保及时的安全更新,否则千里之堤可能毁于蚁穴。所以还是这句话,如果不用SSH,我们不就少一个需要担心的事了吗?

  • 你需要“仅安装一个SSH服务器”来达到目的吗?当然不。你需要加装进程管理器,比如Monit或者Supervisor。这是因为Docker自己只会监视一个进程。如果你需要运行多个进程,你就必须在上面加装一层可以看着他们的应用。换句话说,你在把简单问题复杂化。如果你的应用停了(正常退出或者崩溃),你必须要从你的进程管理日志里面去查看,而不能简单的查看Docker提供的信息。

  • 你可以负责把应用放到容器中,但你是否应该同时负责管理访问策略和安全限制呢?在小机构中,这都不是事。但是在大型机构中,如果你是负责设立应用容器的人,那很可能有另外一个人负责定义远程访问策略。你所在的公司很可能有严格的策略定义说明谁能访问,如何访问或者其他各种审查跟踪的要求。那样的话,你肯定不会被允许把一个SSH服务器扔进你的容器中。

But how do I …

Backup my data?

Your data should be in a volume. Then, you can run another container, and with the --volumes-from option,  share that volume with the first one. The new container will be  dedicated to the backup job, and will have access to the required data.  Added benefit: if you need to install new tools to make your backups or  to ship them to long term storage (like s75pxd or the like), you can do that in the special-purpose backup container instead of the main service container. It’s cleaner.

Check logs?

Use a volume!  Yes, again. If you write all your logs under a specific directory, and  that directory is a volume, then you can start another “log inspection”  container (with --volumes-from, remember?) and do everything you need here. Again, if you need special tools (or just a fancy ack-grep), you can install them in the other container, keeping your main container in pristine condition.

但我该如何做…

备份我的数据?

你的数据应该存在于 volume中. 然后你可以使用--volumes-from选项来运行另一个容器,与第一个容器共享这个volume。这样做的好处:如果你需要安装新的工具(如s75pxd)来将你备份的数据长期保存,或将数据转移到其他永久存储时,你可以在这个特定的备份容器中进行,而不是在主服务容器中。这很简洁。

检查日志?

再次使用 volume! 如果你将所有日志写入一个特定的目录下,且这个目录是一个volume的话,那你可以启动另一个log inspection" 容器(使用--volumes-from,还记得么?)且在这里面做你需要做的事。如果你还需要特殊的工具(或只需要一个有意思的ack-grep),你可以在这个容器中安装它们,这样可以保持主容器的原始环境。

Restart my service?

Virtually all services can be restarted with signals. When you issue /etc/init.d/foo restart orservice foo restart, it will almost always result in sending a specific signal to a process. You can send that signal with docker kill -s <signal>.  Some services won’t listen to signals, but will accept commands on a  special socket. If it is a TCP socket, just connect over the network. If  it is a UNIX socket, you will use… a volume, one more time. Setup the  container and the service so that the control socket is in a specific  directory, and that directory is a volume. Then you can start a new  container with access to that volume; it will be able to use the socket.

“But, this is complicated!” – not really. Let’s say that your service foo creates a socket in/var/run/foo.sock, and requires you to run fooctl restart to be restarted cleanly. Just start the service with -v /var/run (or add VOLUME /var/run in the Dockerfile). When you want to restart, execute the exact same image, but with the --volumes-from option and overriding the command. This will look like this:

# Starting the service
CID=$(docker run -d -v /var/run fooservice)
# Restarting the service with a sidekick container
docker run --volumes-from $CID fooservice fooctl restart

It’s that simple!

重启service?

基本上所有service都可以通过信号来重启。当你使用/etc/init.d/foo restart或service foo restart时,实际上它们都会给进程发送一个特定的信号。你可以使用docker kill -s <signal>来发送这个信号。一些service可能不会监听这些信号,但可以在一个特定的socket上接受命令。如果是一个TCP socket,只需要通过网络连接上就可以了。如果是一个UNIX套接字,你可以再次使用volume。将容器和service的控制套接字设置到一个特定的目录中,且这个目录是一个volume。然后启动一个新的容器来访问这个volume;这样就可以使用UNIX套接字了。

“但这也太复杂了吧!”-其实不然。假设你名为foo的servcie 在/var/run/foo.sock创建了一个套接字,且需要你运行fooctl restart来完成重启。只需要使用-v /var/run(或在Docker文件中添加VOLUME /var/run)来启动这个service就可以了。当你想重启的时候,使用--volumes-from选项并重载命令来启动相同的镜像。像这样:

# Starting the service
CID=$(docker run -d -v /var/run fooservice)
# Restarting the service with a sidekick container
docker run --volumes-from $CID fooservice fooctl restart

很简单吧!

Edit my configuration?

If you are performing a durable change to the configuration, it  should be done in the image – because if you start a new container, the  old configuration will be there again, and your changes will be lost.  So, no SSH access for you! “But I need to change my configuration over the lifetime of my service; for instance to add new virtual hosts!”  In that case, you should use… wait for it… a volume! The configuration  should be in a volume, and that volume should be shared with a  special-purpose “config editor” container. You can use anything you like  in this container: SSH + your favorite editor, or an web service  accepting API calls, or a crontab fetching the information from an  outside source; whatever. Again, you’re separating concerns: one  container runs the service, another deals with configuration updates. “But I’m doing temporary changes, because I’m testing different values! In that case, check the next section!

修改我的配置文件

如果你正在执行一个持久的配置变更,你最好把他的改变放在image中,因为如果你又启动一个container,那么服务还是使用的老的配置,你的配置变更将丢失。所以,没有您的SSH访问!“但是我需要在服务存活期间,改变我的配置;例如增加一个新的虚拟站点!”这种情况下,你需要使用……等待……volume!配置应该在volume中,并且该volume应该和一个特殊目的“配置编辑器”容器共享。你可以在这个容器中使用任何你喜欢的东西:SSH + 你最喜欢的编辑器,或一个接受API调用的web服务,或一个从外部源抓取信息的定时任务;诸如此类。另外,分离关注:一个容器运行服务,另外一个处理配置更新。“但是我做临时更改,因为我正在测试不同的值!”在这种情况下,查看下一章节!

Debug my service?

That’s the only scenario where you really need to get a  shell into the container. Because you’re going to run gdb, strace, tweak  the configuration, etc. In that case, you need nsenter.

Introducing nsenter

nsenter is a small tool allowing to enter into namespaces. Technically, it can enter existingnamespaces,  or spawn a process into a new set of namespaces. “What are those  namespaces you’re blabbering about?” They are one of the essential  constituants of containers. The short version is: with nsenter, you can get a shell into an existing container, even if that container doesn’t run SSH or any kind of special-purpose daemon.

Where do I get nsenter?

Check jpetazzo/nsenter on GitHub. The short version is that if you run:

docker run -v /usr/local/bin:/target jpetazzo/nsenter

This will install nsenter in /usr/local/bin and you will be able to use it immediately. nsenter might also be available in your distro (in the util-linux package).

调试我的应用?

这可能是唯一需要进入container的场景了。因为你要运行gdb, strace, tweak配置,等。这种情况下,你需要 nsenter。

介绍 nsenter

nsenter是一个小的工具,用来进入命名空间中。技术上,它可以进入现有的命名空间,或者产生一个进程进入新的一组命名空间。“命名空间是什么?”他们是容器的重要组成部分。简单点说:通过使用 nsenter ,你可以进入一个已经存在的container中,尽管这个container没有运行ssh 或者任意特殊用途的守护进程。

从哪里获得 nsenter ?

在GitHub上查看 jpetazzo/nsenter 。简单的安装是:

docker run -v /usr/local/bin:/target jpetazzo/nsenter

它将会把 nsenter 安装到 /usr/local/bin 中,你就可以立刻使用它了。

nsenter 也可以在你的发行版中获得(在 util-linux 包中)。

How do I use it?

First, figure out the PID of the container you want to enter:

PID=$(docker inspect --format {{.State.Pid}} <container_name_or_ID>)

Then enter the container:

nsenter --target $PID --mount --uts --ipc --net --pid

You will get a shell inside the container. That’s it. If you want to  run a specific script or program in an automated manner, add it as  argument tonsenter. It works a bit like chroot, except that it works with containers instead of plain directories.

如何使用?

首先,计算出你要进入容器的PID:

PID=$(docker inspect --format {{.State.Pid}} <container_name_or_ID>)

然后进入容器:

nsenter --target $PID --mount --uts --ipc --net --pid

在容器里,可以操作shell解析器。如果要想以自动化的方式来运行特殊的脚本或程序,把它作为参数添加到nsenter中。除了它使用容器代替了简单目录来工作外,它的工作方式有点像chroot。

What about remote access?

If you need to enter a container from a remote host, you have (at least) two ways to do it:

  • SSH into the Docker host, and use nsenter;

  • SSH into the Docker host, where a special key with force a specific command (namely,nsenter).

The first solution is pretty easy; but it requires root access to the  Docker host (which is not great from a security point of view). The  second solution uses the command= pattern in SSH’s authorized_keys file. You are probably familiar with “classic” authorized_keys files, which look like this:

ssh-rsa AAAAB3N…QOID== jpetazzo@tarrasque

远程访问怎么样?

如果你需要从一个远程主机进入一个容器,有(至少)两个方法:

  • SSH 进入 Docker 主机,并使用 nsenter;

  • SSH 进入 Docker 主机,通过一个特殊的密钥参数授权esenter命令  (也就是,nsenter)。

第一种方法相对简单;但是需要root权限访问Docker主机(从安全角度来说不是很好)。第二种方法在 SSH 的 authorized_keys 文件中使用 command= 模式。你可能熟悉 “古典的” authorized_keys文件,它看起来像这样:

ssh-rsa AAAAB3N…QOID== jpetazzo@tarrasque

(Of course, a real key is much longer, and typically spans multiple  lines.) You can also force a specific command. If you want to be able to  check the available memory on your system from a remote host, using SSH  keys, but you don’t want to give full shell access, you can put this in  the authorized_keys file:

command="free" ssh-rsa AAAAB3N…QOID== jpetazzo@tarrasque

Now, when that specific key connects, instead of getting a shell, it will execute the freecommand. It won’t be able to do anything else. (Technically, you probably want to add no-port-forwarding; check the manpageauthorized_keys(5) for  more information.) The crux of this mechanism is to split  responsibilities. Alice puts services within containers; she doesn’t  deal with remote access, logging, and so on. Betty will add the SSH  layer, to be used only in exceptional circumstances (to debug weird  issues). Charlotte will take care of logging. And so on.

(当然,实际上一个真正的密钥是很长的,一般都会占据好几行。)你也可以强制使用一个专有的命令。如果你想要在你的系统上查看一个远程的主机上可以有效使用的内存,可以使用SSH密钥,但是你不会希望交出所有的shell权限,你可以在authorized_keys文件中输入下面的内容:

command="free" ssh-rsa AAAAB3N…QOID== jpetazzo@tarrasque

现在,当使用专有的密钥进行连接时,替换取得的shell,它可以执行free命令。除此之外,就不能做其他的。(通常,你可能还想要添加no-port-forwarding;如果希望了解更多信息可以查看authorized_keys(5)的手册(manpage))。这种机制的关键是使得责任分离。Alice把服务放在容器内部;她不用处理远程的访问,登陆等事务。Betty会添加SSH层,在特殊情况(调试奇怪的问题)下使用。Charlotte会考虑登陆。等等。

Wrapping up

Is it really Wrong (uppercase W) to run the SSH server in a  container? Let’s be honest, it’s not that bad. It’s even super  convenient when you don’t have access to the Docker host, but still need  to get a shell within the container. But we saw here that there are  many ways to not run an SSH server in a container, and still get  all the features we want, with a much cleaner architecture. Docker  allows you to use whatever workflow is best for you. But before jumping  in the “my container is really a small VPS” bandwagon, be aware that  there are other solutions, so you can make an informed decision!

总结

在一个容器中运行SSH服务器,这真的是一个错误(大写字母W)吗?老实说,没那么严重。当你不去访问Docker主机的时候,这样做甚至是极其方便的,但是这仍然需要在容器中取得一个shell。除此之外,我们还有许多方式可以在容器中运行SSH服务器,并能取得所有我们想要的特性,而且其架构还非常清晰。Docker允许你使用任何最适合你的工作流。但是,在做这些之前,迅速步入“我的容器真的是一个小的VPS”这句流行语的(语境)时,请注意还有其他的解决方案,这样你才可以做出一个明智的决定。

返回顶部
顶部