Por que o conmon está em um cgroup diferente quando o podman é iniciado com o systemd?

11

Dado que o podman está instalado em um sistema Linux e em uma unidade systemd chamada baz.service:

# /etc/systemd/system/baz.service
[Service]
ExecStart=/usr/bin/podman run --rm --tty --name baz alpine sh -c 'while true; do date; sleep 1; done'
ExecStop=/usr/bin/podman stop baz

E o baz.service começou:

# systemctl daemon-reload
# systemctl start baz.service

Então, quando verifico o status da unidade, não vejo o processo shnem sleepno cgroup /system.slice/baz.service

# systemctl status baz
● baz.service
   Loaded: loaded (/etc/systemd/system/baz.service; static; vendor preset: enabl
   Active: active (running) since Sat 2019-08-10 05:50:18 UTC; 14s ago
 Main PID: 16910 (podman)
    Tasks: 9
   Memory: 7.3M
      CPU: 68ms
   CGroup: /system.slice/baz.service
           └─16910 /usr/bin/podman run --rm --tty --name baz alpine sh -c while
# ...

Eu esperava ver as crianças she sleepno meu status baz.service porque ouvi pessoas de redhat dizer que podman usa um modelo tradicional de fork-exec.

Se o podman fez fork e exec, meu processo she não sleepseriam filhos do podman e estariam no mesmo cgroup do processo original do podman?

Eu esperava poder usar o systemd e o podman para gerenciar meus contêineres sem que os filhos fossem para um pai diferente e escapassem da minha unidade baz.service ssystemd.

Olhando para a saída de ps, posso ver isso she sleepna verdade são filhos de um processo diferente chamado conmon. Não sei de onde veio o conmon ou como ele foi iniciado, mas o systemd não o capturou.

# ps -Heo user,pid,ppid,comm
# ...
root     17254     1   podman
root     17331     1   conmon
root     17345 17331     sh
root     17380 17345       sleep

A partir da saída, fica claro que minha unidade baz.service não está gerenciando a cadeia de sono conmon -> sh ->.

  • Qual a diferença entre o podman e o modelo do servidor cliente docker?
  • Como o conmon de podman é diferente do container de docker?

Talvez eles sejam ambos tempos de execução de contêiner e o dockerddaemon é o que as pessoas querem se livrar.

Talvez o docker seja como:

  • daemon dockerd
  • docker cli
  • tempo de execução do contêiner

E podman é como:

  • podman cli
  • tempo de execução do contêiner conmon

Então, talvez o podman use um modelo tradicional de exec fork, mas não seja o podman cli que é bifurcado e exec, é o processo de conmon.

Eu me sinto confuso.

mbigras
fonte
Há uma discussão sobre esta questão na lista de discussão podman: lists.podman.io/archives/list/[email protected]/thread/...
mbigras

Respostas:

8

A idéia por trás disso podmané afastar-se da arquitetura centralizada com o superintendente super poderoso (por exemplo dockerd), onde o daemon centralizado é um ponto único de falha. Existe até uma hashtag sobre isso - " #nobigfatdaemons ".

Como evitar o gerenciamento centralizado de contêineres? Você remove o daemon principal único (novamente dockerd) e inicia os contêineres de forma independente (no final do dia, os contêineres são apenas processos, portanto você não precisa do daemon para gerá-los).

No entanto, você ainda precisa do caminho para

  • coletar os logs do contêiner - alguém tem que segurar stdoute stderrdo contêiner;
  • coletar o código de saída do contêiner - alguém precisa fazer wait(2)o PID 1 do contêiner;

Para esse propósito, cada contêiner do podman ainda é supervisionado por um pequeno daemon, chamado conmon(de "monitor de contêiner"). A diferença com o daemon do Docker é que ele é o menor possível (verifique o tamanho do código-fonte ) e é gerado por contêiner. Se conmonum contêiner travar, o restante do sistema não será afetado.

Em seguida, como o contêiner é gerado?

Considerando que o usuário pode querer executar o contêiner em segundo plano, como no Docker, o podman runprocesso bifurca-se duas vezes e só então é executado conmon:

$ strace -fe trace=fork,vfork,clone,execve -qq podman run alpine
execve("/usr/bin/podman", ["podman", "run", "alpine"], 0x7ffeceb01518 /* 30 vars */) = 0
...
[pid  8480] clone(child_stack=0x7fac6bffeef0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[8484], tls=0x7fac6bfff700, child_tidptr=0x7fac6bfff9d0) = 8484
...
[pid  8484] clone(child_stack=NULL, flags=CLONE_VM|CLONE_VFORK|SIGCHLD <unfinished ...>
[pid  8491] execve("/usr/bin/conmon", ... <unfinished ...>
[pid  8484] <... clone resumed>)        = 8491

O processo intermediário entre podman rune conmon(ou seja, o pai direto de conmon- no exemplo acima, é PID 8484) será encerrado e conmonserá reparado init, tornando-se assim um daemon autogerenciado. Depois disso, conmontambém obtém o tempo de execução (por exemplo runc) e, finalmente, o tempo de execução executa o ponto de entrada do contêiner (por exemplo /bin/sh).

Quando o contêiner está em execução, podman runnão é mais necessário e pode sair, mas, no seu caso, permanece on-line, porque você não solicitou a desanexação do contêiner.

Em seguida, podmanutiliza cgroups para limitar os contêineres. Isso significa que ele cria novos cgroups para novos contêineres e move os processos para lá . Pelas regras dos cgroups, o processo pode ser membro de apenas um cgroup de cada vez, e adicionar o processo a algum cgroup o remove de outro cgroup (onde estava anteriormente) dentro da mesma hierarquia. Portanto, quando o contêiner é iniciado, o layout final dos cgroups é semelhante ao seguinte: podman runpermanece em cgroups do baz.service, criado por systemd, o conmonprocesso é colocado em seus próprios cgroups e os processos em contêineres são colocados em seus próprios cgroups:

$ ps axf
<...>
 1660 ?        Ssl    0:01 /usr/bin/podman run --rm --tty --name baz alpine sh -c while true; do date; sleep 1; done
 1741 ?        Ssl    0:00 /usr/bin/conmon -s -c 2f56e37a0c5ca6f4282cc4c0f4c8e5c899e697303f15c5dc38b2f31d56967ed6 <...>
 1753 pts/0    Ss+    0:02  \_ sh -c while true; do date; sleep 1; done
13043 pts/0    S+     0:00      \_ sleep 1
<...>

$ cd /sys/fs/cgroup/memory/machine.slice
$ ls -d1 libpod*
libpod-2f56e37a0c5ca6f4282cc4c0f4c8e5c899e697303f15c5dc38b2f31d56967ed6.scope
libpod-conmon-2f56e37a0c5ca6f4282cc4c0f4c8e5c899e697303f15c5dc38b2f31d56967ed6.scope

$ cat libpod-2f56e37a0c5ca6f4282cc4c0f4c8e5c899e697303f15c5dc38b2f31d56967ed6.scope/cgroup.procs 
1753
13075

$ cat libpod-conmon-2f56e37a0c5ca6f4282cc4c0f4c8e5c899e697303f15c5dc38b2f31d56967ed6.scope/cgroup.procs 
1741

Nota: O PID 13075 acima é realmente um sleep 1processo, gerado após a morte do PID 13043.

Espero que isto ajude.

Danila Kiver
fonte
11
"cria novos cgroups para novos contêineres e move os processos para lá" Eu não entendo por que o podman está fazendo esse trabalho em vez do systemd. Você pode adicionar uma explicação sobre por que usamos o conmon para manter stdout e stderr em vez de systemd? Ao aprender sobre o systemd, pensei que o objetivo do systemd é gerenciar processos e executar tarefas como capture stdout / stderr, descobrir o status da saída e lidar com a reinicialização.
mbigras
2
O Podman gerencia os cgroups porque ele é o proprietário do contêiner e tem que garantir que o contêiner funcione independentemente do sistema de inicialização que você possui. O Systemd gerencia cgroups para serviços porque possui serviços (por padrão, os serviços não devem gerenciar cgroups, embora o systemd suporte alguns tipos de delegação - consulte systemd.io/CGROUP_DELEGATION ). Se você quiser que o podman reutilize os cgroups criados pelo systemd para o serviço, deve haver um suporte do lado do podman, e atualmente não vejo um (embora eu possa errar).
Danila Kiver
11
Quanto a stdout/ stderrfluxos - novamente, podmanpossui o contêiner e captura os fluxos do processo em contêiner. systemdpossui o serviço e captura os fluxos do processo principal do serviço (no seu caso, systemdrealmente captura stdout/ stderrdo podman runprocesso). Isso funciona exatamente como deveria funcionar, porque conmoncaptura os fluxos do contêiner, podman runanexa a conmon, systemdcaptura os fluxos de podman runmodo que, finalmente, todos os logs do contêiner são capturados systemde você os vê systemctl status baz.service.
Danila Kiver