DevSecOps

26 abr, 2018

Rodando Docker a partir do WSL no Windows

Publicidade

Para acessar o Docker a partir do Windows Subsystem for Linux (WSL), eu costumava abrir a porta 2375 no Docker host e configurar no meu .bashrc, a variável de ambiente DOCKER_HOST para o valor :2375. Isso resolvia o problema, mas deixava meu sistema exposto externamente (ainda que o firewall do Windows bloqueasse o acesso, me incomodava).

Agora, na versão 1803 do Windows – que está pra sair, ou já saiu; quando eu escrevi esse artigo estava quase -, é possível utilizar sockets Unix no Windows. Eu poderia desligar isso e utilizar o socket padrão do Docker. É exatamente a proposta deste artigo de Command Line do Windows, mas ele só resolve metade do problema, porque você precisa – toda vez que abrir o bash do WSL – rodar um comando para habilitar o encaminhamento do socket. E pior, como root. E se algo der errado o arquivo de socket fica lá morto e você precisa arrumar na mão. Vamos resolver esses detalhes.

Resolvi isso mudando o socket de lugar. Então, para resumir, vou explicar desde o começo para quem quiser experimentar.

Instale o Go

Instalar o Go é basicamente copiar alguns arquivos. Siga o script abaixo:

#atualize suas fontes
sudo apt-get update
#baixe a última versão
sudo wget https://dl.google.com/go/go1.10.1.linux-amd64.tar.gz
#dezipe
sudo tar -C /usr/local -xzf go1.10.1.linux-amd64.tar.gz
#Coloque os binários no path
export PATH=$PATH:/usr/local/go/bin

Inclua a última linha do script acima, também no seu script .bashrc (ou o seu script de startup, caso você use zsh ou outros), dessa forma, no próximo início do Shell, você continuará tendo o Go no PATH.

Em seguida vamos buildar e instalar o npiperelay, que permite acessar named pipes do Windows a partir do WSL. O binário final do npiperelay pode ficar em qualquer lugar, desde que seja do lado Windows do sistema de arquivos.

Não tente colocar do lado WSL das coisas, ou o WSL vai tentar executá-lo direto, e não funcionar. Esse binário é um binário Windows, e será executado sem intermédio do WSL. Ele não é um binário do Linux. Note que você precisará substituir o seu nome de usuário nos scripts a seguir:

go get -d github.com/jstarks/npiperelay
GOOS=windows go build -o /mnt/c/Users/<seu-usuario-no-windows>/go/bin/npiperelay.exe github.com/jstarks/npiperelay

Em seguida, instale o socat que vai encaminhar as chamadas do socket do Linux para o Windows.

sudo apt install socat -y

E vamos instalar também o Docker no WSL, seguindo as instruções de Ubuntu do site do Docker:

curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh

Não se esqueça de adicionar seu usuário no grupo do docker:

sudo usermod -aG docker seu-usuario-no-linux

Em seguida, crie o arquivo docker-relay em algum lugar onde você possa executar binários. Eu coloquei no meu diretório ~/bin porque ele está no PATH. Outra opção é o no /usr/local/bin, que costuma já estar no PATH:

#!/bin/sh
SOCKET=/home/seu-usuario-no-linux/sockets/docker.sock
#remove o arquivo de socket se ele não foi devidamente excluído da última vez
if [ -e $SOCKET ]; then rm $SOCKET; fi
exec socat UNIX-LISTEN:$SOCKET,fork,group=docker,umask=007 EXEC:"/mnt/c/Users/seu-usuario-no-windows/go/bin/npiperelay.exe -ep -s //./pipe/docker_engine",nofork

Não deixe de criar o diretório pro socket que é mencionado no arquivo acima:

mkdir ~/sockets

Testando se funcionou

Saia e entre novamente no bash para garantir que o grupo do Docker está ativo, e então, rode:

docker-relay

Garanta que o Docker está rodando: abra outra janela do WSL e digite:

docker version

O resultado deve ser parecido com este, abaixo. No meu caso, estava rodando com contêineres Windows, então note que o client é Linux (o WSL), mas o server é Windows:

Client:
 Version:       18.03.0-ce
 API version:   1.37
 Go version:    go1.9.4
 Git commit:    0520e24
 Built: Wed Mar 21 23:10:01 2018
 OS/Arch:       linux/amd64
 Experimental:  false
 Orchestrator:  swarm
 
Server:
 Engine:
  Version:      18.03.0-ce
  API version:  1.37 (minimum version 1.24)
  Go version:   go1.9.4
  Git commit:   0520e24
  Built:        Wed Mar 21 23:21:06 2018
  OS/Arch:      windows/amd64
  Experimental: true

Para garantir que está ok, rode ps aux | grep socat, e você verá o socat rodando, iniciado pelo nosso script docker-relay anterior.

Rodando automaticamente

O chato é que, para isso funcionar, você precisaria, toda vez que iniciar o WSL, rodar docker-relay & (o & é pra desatachar o terminal e deixar em segundo plano). Além disso, ao sair do WSL o processo do socat morre, ou seja, toda vez que você inicia um novo WSL você teria que iniciar o socat, e isso leva tempo.

Pra resolver isso, vamos iniciar uma sessão de tmux colocando somente o socat rodando lá. O Tmux, ou “Terminal Multiplexer” permite rodar outros terminais dentro da mesma janela, estando anexados na janela atual ou não. O Tmux é muito legal por vários motivos, e aqui ele será útil por esse. Aprenda Tmux depois, se você já não conhece. Colocaremos o socat em uma sessão do Tmux e, ao sair do WSL, o socat continuará rodando.

Coloque nos seus scripts de inicialização (novamente, seu .bashrc ou arquivo preferido), o seguinte script:

if cat /proc/version | grep Microsoft > /dev/null; then
  export WSL=true
fi
if [ "$WSL" ]; then
  export DOCKER_HOST="unix://$HOME/sockets/docker.sock"
  if ! pgrep socat > /dev/null; then
    tmux new -s docker-relay-session -d docker-relay
  fi
fi

Primeiro verificamos se estamos no WSL. Eu faço isso porque compartilho meus arquivos entre Linux com kernel nativo e o WSL. Tenho um repositório no GitHub, em giggio/bashscripts caso você queira ver o que estou usando. Assim, caso o script rode direto no Linux, não preciso tentar encaminhar o socket.

Em seguida, exponho o host do docker no novo socket que está na minha home, no diretório criado anteriormente, e então verifico se o socat está rodando, com pgrep. Se estiver, eu não tento rodar ele novamente. Se formos rodar, rodamos com Tmux.

Com tudo isso no lugar, feche todas as janelas do Bash e abra uma novamente. Tente rodar um docker version e confirme se funciona. Rode um ps aux | grep socat e confirme que ele está rodando também, e anote o número do processo. Saia do WSL, e abra o task manager, e clique em “More details” no canto inferior esquerdo.

O Task Manager é capaz de exibir os processos do WSL (do Linux) rodando. Procure pelo socat, ele deve estar lá. Abra novamente o WSL, rode o ps aux | grep socat e confirme se o número do processo do socat é o mesmo de antes.

É isso. Dessa forma o seu Docker fica seguro, sem a porta 2375 exposta, e o acesso ao Docker continua funcionando. Analisei a demanda de memória, e o socat ocupa pouco mais de 1.5kb e o tmux um pouco menos. No total, dá 3kb, ou seja, o impacto é irrisório.

Bônus: rodando contêineres Linux sem VM

Pra terminar bonito, coloque o Docker em modo de contêineres Windows e rode o seguinte comando:

docker run --rm -ti --platform=linux alpine

Agora, para inception: você está dentro de um terminal do Alpine Linux, através de um socket Unix encaminhado para um named pipe do Windows, falando com um host Windows do Docker que está rodando um contêiner Linux sem máquina virtual (veja aqui como isso é possível).

Bônus 2: rodando Windows e compartilhando volumes

Agora rode:

docker run --rm -ti -v c:\\windows:c:\\windowshost  microsoft/windowsservercore

Dentro do contêiner, rode dir \windowshost para ver os arquivos do host.

Nesse caso, você está rodando um contêiner Windows e compartilhando um volume que aponta pro diretório c:\windows. Não é possível compartilhar diretórios do WSL, você precisará utilizar um diretório que esteja visível para o Windows. E nunca utilize o /mnt/c/, ele não vai funcionar.

É isso. Dá um pouquinho de trabalho, mas o resultado fica bom. Você curtiu? O que achou desse hack todo?

***

Este artigo foi produzido em parceria com a Lambda3. Leia outros conteúdos no blog da empresa: blog.lambda3.com.br