Viagem ao centro ... imagem do docker. Ou como baixar uma imagem do registro sem janela de encaixe

Três dias antes do ano novo, havia uma tarefa de transferir nosso software para o cliente através do gerente, em uma unidade flash. O software é uma plataforma de microsserviço com várias dezenas de imagens de docker com muitas configurações e um gráfico de leme com um quilômetro de comprimento. O que tínhamos:


  • Gerente em Moscou (não sou de lá)
  • Windows
  • Não há interação direta (e, se fosse, realmente não ajudava)
  • estivador não

Pff, pensei! Vou pegar Golang, escrever um programa, compilar para Windows.
... e cinco horas depois eu percebi a pressa das minhas conclusões. Nesse momento, o riso de Nelson se lembrou pela primeira vez. HA HA O que me assombrava o tempo todo que passava estudando a questão.


A maioria dos exemplos que encontrei requer dockerd. Dois scripts que não usam dockerd, encontrados após uma hora de pesquisa no Google, um e dois . A primeira opção me ajudou a entender o processo de obtenção de todas as camadas de imagem e arquivos de configuração, mas é impossível usá-lo no Windows. E a segunda opção indicava que não eram apenas vários hashes piscando na tela, especificamente este FIXME . Pode-se, é claro, parar por aí, funciona! Transferir para ir não é difícil. Mas como verificar se as imagens do gerente estavam na mesma forma que em nosso registro? Mas de jeito nenhum! Portanto, enviei apenas imagens para o armazenamento compartilhado, baixei usando o comando docker save e compartilhei o link. E se acalmou com isso.


No quarto dia de férias, bastante cansada deles, a idéia de baixar e montar a imagem do docker me tomou novamente, e eu mergulhei no código moby por algumas horas.


O que eu tive desta vez:


  • Entendendo como obter todas as camadas

Tomando o Python "na mão" e com base nesse script , decidi corrigi-lo. No segundo dia, decidi escrever um roteiro do zero. Lembrando-me dos meus trabalhos para autorização automática, simplesmente copiei parte do código de lá, mais os desenvolvimentos já feitos por mim ao editar o script. Não houve problemas com a autorização e o download desses dados, mas surgiram questões:


  • quais são os hashes exibidos quando o comando pull do docker é executado?
  • quais são os hashes usados ​​para nomear diretórios dentro do tarball da imagem?
  • Como construir um arquivo tar para que a soma da verificação corresponda à imagem original?

Para estudar, escolhi uma imagem do ubuntu: 18.04
imagem sha256sum salva por meio do docker save - 257cab9137419a53359d0ed76f680fe926ed3645238357bdcdb84070a8f26cd0.


 > docker pull ubuntu:18.04 18.04: Pulling from library/ubuntu 2746a4a261c9: Downloading [==============> ] 6.909MB/26.69MB 4c1d20cdee96: Download complete 0d3160e1d0de: Download complete c8e37668deea: Download complete Digest: sha256:250cc6f3f3ffc5cdaa9d8f4946ac79821aafb4d3afc93928f0de9336eba21aa4 Status: Downloaded newer image for ubuntu:18.04 docker.io/library/ubuntu:18.04 

Conteúdo do tarball da imagem


 tar tvf ubuntu.tar drwxr-xr-x 0 root root 0 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/ -rw-r--r-- 0 root root 3 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/VERSION -rw-r--r-- 0 root root 477 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/json -rw-r--r-- 0 root root 991232 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/layer.tar drwxr-xr-x 0 root root 0 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/ -rw-r--r-- 0 root root 3 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/VERSION -rw-r--r-- 0 root root 477 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/json -rw-r--r-- 0 root root 15872 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/layer.tar -rw-r--r-- 0 root root 3411 Dec 19 11:21 549b9b86cb8d75a2b668c21c50ee092716d070f129fd1493f95ab7e43767eab8.json drwxr-xr-x 0 root root 0 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/ -rw-r--r-- 0 root root 3 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/VERSION -rw-r--r-- 0 root root 1264 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/json -rw-r--r-- 0 root root 3072 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/layer.tar drwxr-xr-x 0 root root 0 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/ -rw-r--r-- 0 root root 3 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/VERSION -rw-r--r-- 0 root root 401 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/json -rw-r--r-- 0 root root 65571328 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/layer.tar -rw-r--r-- 0 root root 432 Jan 1 1970 manifest.json -rw-r--r-- 0 root root 88 Jan 1 1970 repositories 

Configuração da imagem


 { ... "rootfs": { "type": "layers", "diff_ids": [ "sha256:2dc9f76fb25b31e0ae9d36adce713364c682ba0d2fa70756486e5cedfaf40012", "sha256:9f3bfcc4a1a8a676da07287a1aa6f2dcc8e869ea6f054c337593481a5bb1345e", "sha256:27dd43ea46a831c39d224e7426794145fba953cd7309feccf4d5ea628072f6a2", "sha256:918efb8f161b4cbfa560e00e8e0efb737d7a8b00bf91bb77976257cd0014b765" ] } ... } 

A primeira pergunta foi resolvida com bastante rapidez, com a ajuda da documentação https://github.com/opencontainers/image-spec/blob/master/config.md . Os hashes que aparecem quando você executa o comando docker pull são chainIDs calculados na lista diff_ids do manifesto image-config, onde o primeiro chainID é sempre o primeiro da lista diff_ids e os subseqüentes são somas de hash da cadeia de caracteres (chain_id[i-1] + " " + diff_id[i]) Código para criar chainID chainID:


 def chain_ids(ids: list) -> list: chain = list() chain.append(ids[0]) if len(ids) < 2: return ids nxt = list() nxt.append("sha256:" + hashlib.sha256(f'{ids[0]} {ids[1]}'.encode()).hexdigest()) nxt.extend(ids[2:]) chain.extend(chain_ids(nxt)) return chain 

A adição de um prefixo com o nome do algoritmo, neste caso "sha256:", é obrigatória e está incluída nos requisitos do padrão opencontainers, ou seja, a string deve estar no formato "algoritmo: hash".


Ele passou duas noites em uma pergunta sobre como nomear um diretório. Por um longo tempo, observei o código-fonte do docker-daemon e o Oh! um milagre! Consegui encontrar o código de geração aqui e aqui . Para gerar o nome do diretório, é necessário calcular o hash na configuração da camada json. O Docker possui várias versões de configuração e, antes do docker engine 1.9, as configurações da versão v1 eram usadas. Mal disse o que fez! E novamente, a silhueta de Nelson surge. Após uma pequena depuração, percebi que o problema estava oculto na geração de json. No Python, a ordem dos dados em um dicionário pode diferir da ordem dos dados em json gerados a partir deste dicionário. A ordem dos dados em json será diferente e, portanto, seu hash também será diferente. Eu tive que ir para OrderedDict, pré-registrar a ordem de dados desejada neles. Isso aumentou o tamanho do código em uma vez e meia.


Parece que tudo está corrigido, eu corro o script e ... em algum lugar lá no fundo, o notório HA-HA aparece! O último hash não corresponde. Mais uma vez, estudo o código e vejo , e essa é outra v1-config que contém todas as informações sobre a imagem, que podem ser vistas usando o docker inspect . Adicione outro OrderDict especificamente para ele, complemente o código e ... HA HA!
Já eram cinco da manhã e minha cabeça realmente não pensava; então, depois de dormir, voltei a visualizar o código. Observar repetidamente o código de geração encontrou uma linha . Como fiquei feliz em ver isso. Antes de vê-la, havia pensamentos de construir minha janela de encaixe com Jack preto e registro de dados. docker save o debug, executo o docker save e ... não é nada engraçado, no docker-desktop para mac os há um limite no comprimento da linha no log de 947 caracteres e a configuração gerada é interrompida para \" . Após executar todas essas etapas no Linux, consegui obter a configuração camada da primeira versão, com base na qual escrevi o código, e consegui obter o hash desejado da última camada. Os hashes para todos os arquivos são iguais, os diretórios são nomeados da mesma maneira que a imagem original. É hora de coletar o arquivo tar ... HAHA!


O tamanho do arquivo não corresponde, leia https://github.com/opencontainers/image-spec/blob/master/layer.md e o formato de arquivo tar . O valor padrão é 10240 bytes, e o tamanho do arquivo morto que eu coletei é 9216 bytes maior. Inicialmente, pensei que era necessário reduzir o tamanho do bloco para 1024 bytes, o que resultou incorreto e, como resultado, o tamanho do bloco de 512 bytes igualou o tamanho dos arquivos.


 tarfile.RECORDSIZE = 512 

A primeira linha do arquivo recém-criado contém a pasta raiz "/". Esta opção não é adequada, portanto, complemento o código, verificando o conteúdo da pasta e adicionando-o individualmente ao arquivo, depois de o ter classificado anteriormente.


Finalmente, conseguimos obter o mesmo tamanho de arquivo, uma aparência uniforme de diretórios, mas isso não é tudo. Arquivos e diretórios, com exceção de manifest.json e respoitories, devem ter atributos st_atime no archive, st_mtime igual a st_ctime. Para arquivos manifest.json e respoitories, os atributos st_atime, st_mtime e st_ctime devem estar datados do início da era 1970-01-01 00:00 . Todas as datas devem ser definidas de acordo com o fuso horário, respectivamente. Desde que realizei todo o trabalho no Mac OS, notei uma diferença. Ao salvar a imagem no Linux, a lista de arquivos no arquivo ficou assim:


 tar tvf ubuntu.tar drwxr-xr-x 0/0 0 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/ -rw-r--r-- 0/0 3 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/VERSION -rw-r--r-- 0/0 477 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/json -rw-r--r-- 0/0 991232 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/layer.tar drwxr-xr-x 0/0 0 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/ -rw-r--r-- 0/0 3 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/VERSION -rw-r--r-- 0/0 477 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/json -rw-r--r-- 0/0 15872 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/layer.tar -rw-r--r-- 0/0 3411 Dec 19 11:21 549b9b86cb8d75a2b668c21c50ee092716d070f129fd1493f95ab7e43767eab8.json drwxr-xr-x 0/0 0 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/ -rw-r--r-- 0/0 3 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/VERSION -rw-r--r-- 0/0 1264 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/json -rw-r--r-- 0/0 3072 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/layer.tar drwxr-xr-x 0/0 0 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/ -rw-r--r-- 0/0 3 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/VERSION -rw-r--r-- 0/0 401 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/json -rw-r--r-- 0/0 65571328 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/layer.tar -rw-r--r-- 0/0 432 Jan 1 1970 manifest.json -rw-r--r-- 0/0 88 Jan 1 1970 repositories 

Diferentemente da lista no início do artigo, no Linux, o arquivo é salvo com o sinalizador somente numérico. Existem duas variáveis ​​no objeto tarinfo responsáveis ​​por isso, tarinfo.uname e tarinfo.gname. E o segundo problema com o mac os é a falta de um grupo raiz, ele é corrigido usando a variável tarinfo.gid no mesmo objeto tarinfo. Bem, tudo parece estar criando um arquivo ...



Para todos os arquivos, o hash converge, os nomes dos diretórios e arquivos são os mesmos, os atributos st_atime, st_mtime e st_ctime convergem para o original, os direitos para os arquivos são exatamente os mesmos. A abertura dos dois arquivos em um editor hexadecimal viu uma pequena diferença:



A janela superior é o original, o arquivo inferior que eu coletei.


Compreendendo o formato tar. Depois do nome do diretório está o valor dos direitos para o arquivo (retângulo laranja). A diferença é que as informações sobre o objeto que está sendo adicionado ao arquivo morto não são adicionadas aos direitos do diretório. Um valor 40755 indicado nos direitos indica que este é um diretório com permissões de 755 e 100644 é um arquivo com direitos de 644. O retângulo vermelho é uma sequência mágica e, a julgar pelo código do arquivo ustar\000 sequência mágica ustar\000 , é usado apenas nos formatos PAX e USTAR. O formato PAX não se encaixa, ele usa um tipo especial de cabeçalho. O retângulo azul é uma soma de verificação e difere devido ao uso de diferentes formatos para direitos de gravação em um arquivo e cabeçalho mágico.


Mudo o formato de arquivo para USTAR, mas não está claro o que fazer com as permissões de gravação de arquivos. Aqui e aqui , a mágica está acontecendo para mim, nunca trabalhei com o sistema octal e não entendo por que o e comercial é necessário aqui (alguém pode compartilhar seu conhecimento nos comentários). Eu tive que adicionar várias impressões para ver quais dados chegam com argumentos e quais dados são usados ​​para formar o bloco de arquivamento. Tomando um número inteiro da segunda posição do bloco de dados, era 16877 e levando-o ao cálculo octal, descobriu-se que esse valor é 0o40755 , que é 0o40755 que eu preciso. Substituindo as funções get_info e _create_header, excluindo & 0o7777 delas (nada mais me ocorreu), consegui coletar um arquivo tar com um hash sha256 que coincidia com o original.


PS Enquanto escrevia um artigo, a imagem do ubuntu foi atualizada: 18.04 em hub.docker.com. Então eu tive que baixar a imagem no Digest. Os valores de hash não correspondem mais ao original devido ao fato de o Digest ter sido gravado em vez da tag, caso contrário, eram imagens idênticas.


A segunda descoberta para mim foi a ausência de um arquivo de repositórios no archive ao salvar uma imagem com uma tag ausente usando o docker save .


Código de funcionamento completo aqui: https://github.com/myback/docker_pull


Imagem de Nelson Manz, bem como sua risada "HA-HA" é propriedade da FOX :)

Source: https://habr.com/ru/post/pt484374/


All Articles