Docker больше не нужен

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Docker vs. Podman

В этой статье мы не будем подробно разбирать базовые функции Podman и отличия этого проекта от Docker.
Такой информации хватает в интернете.
Вместо этого, мы на конкретном примере (простое Java приложение) разберем как можно заменить Docker на Podman в конвейере CI/CD,
и какие это может принести дивиденды.

Podman – это инструмент с открытым исходным кодом для работы с образами контейнеров.
Является утилитой командной строки с аналогичными docker командами, однако не требует дополнительный сервис для работы и может работать без привилегий root.

Установка

Пакеты Podman доступны для большинства дистрибутивов Linux и не требуют настройки дополнительных репозиториев:

yum install podman buildah skopeo

Сборка Podman из исходного кода гораздо проще, чем компиляция бинарных файлов Docker.

Компоненты Podman

Компонент

Описание

libpod

Управление жизненным циклом контейнеров

crun/runc

Среда выполнения контейнеров

containers/image

Управление образами контейнеров

containers/storage

Управление томами, файловой системой и хранилищем данных для контейнеров и образов

Buildah

Сборка контейнеров

Conmon

Мониторинг среды выполнения контейнеров

Container Network Interface (CNI)

Управление сетями реализовано на основе спецификаций Кубернетес CNI

Отношение между компонентами Podman:

2
2

Как Podman запускает контейнеры:

3
3

Docker Compose

Podman полностью совместим с Docker Compose, однако для это необходимо активировать сервис сокета в системе:

systemctl enable --now podman.socket

docker-compose нужен docker.sock, поэтому Podman при установке создает символическую ссылку на этот файл:

ls -al /run/docker.sock

API

После активации сервиса сокета можно проверить REST API Podman с помощью curl:

curl --unix-socket /run/podman/podman.sock http://d/v3.0.0/libpod/images/json

В данном случае вы должны получить список со всеми образами контейнеров в системе. Большинство функции Podman CLI доступны через REST API.

Перемещение образов между машинами

Podman позволяет перемещать образы контейнеров между хостами, без использования реестров контейнеров. Сначала нужно добавить новое SSH соединение:

podman system connection add <CONNECTION> -i ~/.ssh/id_ed25519 REMOTE_USER_NAME@<IP_ADDRESS>/run/user/1000/podman/podman.sock

Затем копируем образ с помощью команды podman image scp:

podman image scp my_image <CONNECTION>

Тестовый проект

В качестве примера будет использоваться тестовый проект на Java Spring Boot. Для сборки Java будет использоваться Gradle.
Для оркестрации Jenkins.

Генерируем исходный код проекта Spring Boot

Spring Boot дает возможность с помощью встроенных средств создавать скелет проекта: директории и исходный код для сборных инструментов экосистемы Java – Maven и Gradle. Для этого можно использовать интерфейс командной строки, веб-форму, в данном случае вы будете использовать утилиту curl:

curl https://start.spring.io/starter.tgz \
    -d applicationName=HelloApplication \
    -d artifactId=demo \
    -d baseDir=hello \
    -d bootVersion=2.7.0 \
    -d dependencies=web,actuator \
    -d description='Demo project for Spring Boot' \
    -d groupId=com.example \
    -d javaVersion=17 \
    -d language=java \
    -d name=demo \
    -d packageName=com.example.demo \
    -d packaging=jar \
    -d type=gradle-project \
    -d version=0.0.1-SNAPSHOT \
| tar -xzvf -

После выполнения этой команды, в текущей директории должен появится каталог приложения под названием hello:

Для того, чтобы вывести список с файлами в директории выполните команду ls hello.
Планируемый результат:

HELP.md		build.gradle	gradle		gradlew		gradlew.bat	settings.gradle	src

Нужно будет добавить код в директорию сгенерированного приложения, для того, чтобы скомпилировать проект.

Добавляем логику

Ниже приведен пример кода, который нужно добавить в скелет проекта.
HelloApplication.java:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class HelloApplication {

    @RequestMapping("/")
    public String home() {
        return "Hello world\n";
    }

    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }

}

Добавите этот код в проект:

cp -f HelloApplication.java hello/src/main/java/com/example/demo/

Выполнить команду: ls hello/src/main/java/com/example/demo/
Планируемый результат:

HelloApplication.java

Dockerfile

Для работы вы будете использовать следующий Dockerfile:
cat Dockerfile:

## Stage 1 : Gradle компилирует исходный код в исполняемый jar
FROM openjdk:17-oraclelinux8 as builder
RUN microdnf install findutils
WORKDIR /home/gradle/src
COPY . .
RUN ./gradlew build

## Stage 2 : Финальный образ который содержит JRE и jar
FROM openjdk:17-oraclelinux8

# Копируем jar в образ контейнера
COPY --from=builder /home/gradle/src/build/libs/demo-0.0.1-SNAPSHOT.jar demo.jar

EXPOSE 8080

# Запускаем приложение используя исполняемый jar файл
CMD ["java", "-jar", "demo.jar"]

Следующая стадия определяет контейнер который содержит только среду выполнения Java (Java runtime).
Jar-файл из предыдущей стадии копируется в новый контейнер, а остальные инструкции нужны, чтобы запустить с нужными параметрами приложение Spring Boot, которое мы создали.

Скопируйте Dockerfile в проект:

cp -f Dockerfile hello

Выполнить команду: ls hello/Dockerfile
Планируемый результат:

hello/Dockerfile

Build

Buildah это инструмент который работает в связке с Podman. Кодовая база Podman включает в себя пакеты Buildah.

Buildah не нужны привилегии root для сборки образов, что является несомненным плюсом с точки зрения безопасности.

В отличии от Docker, Buildah позволяет автоматизировать сборку образа с помощью Bash скриптов, а не только Dockerfile.
Директивы Dockerfile/Containerfile можно использовать в CLI:

  • buildah from

  • buildah run

  • buildah config --entrypoint/entrypoint.sh

  • buildah add

  • buildah copy

В данном случае мы используем для сборки стандартного Dockerfile:

    stage('Build image') {
      steps {
        dir('k8s-course/podman/helloPodman') {
          sh 'buildah build -f Dockerfile -t antonvkrylov/k8s-course:helloPodman'
        }
      }
    }

После запуска сборки в логах можно увидеть вывод Gradle:

4
4

Основные конфигурационные файлы, которые используются Buildah:

/etc/containers/registries.conf - Реестры контейнеров.
/usr/share/containers/policy.json - Верификация сигнатур.
/usr/share/containers/mounts.conf - Директории и файлы которые автоматически монтируются в контейнерах.
/usr/share/containers/seccomp.json - Системные вызовы которые разрешены для выполнения в контейнерах.

Push

Загрузка собранного образа в реестр ничем не отличается от Docker:

    stage('Push image') {
      steps {
        dir('k8s-course/podman/helloPodman') {
          sh 'buildah push antonvkrylov/k8s-course:helloPodman'
        }
      }
    }

Так же как и с Docker для аутентификации используется команда podman/buildah login

Buildah так же как и Podman умеет работать c аутентификационными файлами:
buildah push --authfile /auth.json registry.example.com

Test

Podman позволяет проверять состояние контейнера и приложения с помощью специальных проверок (healthchecks).
Механизм проверок похож на то, что было реализовано в последних версиях Docker Compose.
С помощью этой функции, можно встраивать в конвейер CI простые функциональные и интеграционные тесты.

1
1
    stage('Test') {
    /////////
    /// Jenkins позволяет выполнять стадии или шаги внутри одной стадии параллельно
    /////////
      parallel {
        stage('Test ports') {
          steps {
            /////////
            /// Мы создаем проверку (healthcheck) с определенными параметрами, которая выполняет запрос с помощью curl внутри контейнера.
            /////////
            sh"""
            podman run --rm -dt --name test_ports \
            --healthcheck-start-period 10s \
            --healthcheck-retries 1 \
            --healthcheck-command "CMD-SHELL curl -f http://127.0.0.1:8080 || exit 1" docker.io/antonvkrylov/k8s-course:helloPodman
            """
            /////////
            /// Если таймаут в 2 минуты будет привешен то конвейер прервется с ошибкой и статусом «Aborted»
            /////////
            timeout(time: 2, unit: 'MINUTES') {
            /////////
            /// В этом скрипте мы проверяем статус проверки через каждые 3 секунды – если статус «healthy» то цикл завершается.
            /////////
            sh"""
            #!/bin/bash
            while [ "`podman healthcheck run test_ports`" = "unhealthy" ]; do
                sleep 3
            done
            """
            }
          }
        }
        stage('Test Java') {
          steps {
            /////////
            /// Мы создаем проверку (healthcheck) с определенными параметрами, которая выполняет запрос с помощью curl внутри контейнера.
            /////////
            sh"""
            podman run --rm -dt --name test_java \
            --healthcheck-start-period 10s \
            --healthcheck-retries 1 \
            --healthcheck-command "CMD-SHELL java --version | grep 'openjdk 17.0.2' || exit 1" docker.io/antonvkrylov/k8s-course:helloPodman
            """
            timeout(time: 2, unit: 'MINUTES') {
            sh"""
            #!/bin/bash
            while [ "`podman healthcheck run test_java`" = "unhealthy" ]; do
                sleep 3
            done
            """
            }
          }
        }
      }
    }

Scan

Для сканирования собранного образа на уязвимости можно использовать один из популярных инструментов для статического анализа уязвимостей (SAST):

    stage('Scan image') {
      steps {
        dir('k8s-course/podman/helloPodman') {
          sh 'trivy image --security-checks vuln --severity CRITICAL antonvkrylov/k8s-course:helloPodman'
        }
      }
    }

Результаты сканирования для каждой сборки будут сохранятся в Jenkins:

5
5

Archive

Skopeo используется для копирования и перемещения образов между реестрами контейнеров. В данном случае образ контейнера из публичного реестра с помощью skopeo сохраняется для архивации на диске:

    stage('Archive') {
      steps {
        sh"""
        skopeo copy docker://antonvkrylov/k8s-course:helloPodman --dest-compress --dest-compress-format gzip docker-archive:./helloPodman.tar.gz
        """
      }
    }
  }

Skopeo можно использовать для реверс-инжиниринга образов, в случаях, когда не доступа к Dockerfile:

skopeo inspect docker://camunda/camunda-bpm-platform:wildfly-SNAPSHOT

skopeo copy docker://camunda/camunda-bpm-platform:wildfly-SNAPSHOT dir://tmp/camunda

tree /tmp/camunda/
/tmp/camunda/
├── 0cdfa0c98ed79707cd91c5dd7ebd282aa2b976d86a9e699d7fc188cdb6be390e
├── 16e3566755e5dc026d1a6bcc28c8edc7a7aa6d068af8a5b9e10478c1dd900682
├── 410f32425c8a73d3e62330ab499ae85a1ce97fec74e572c2b50af4d654d44649
├── 4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1
├── c2005d18c6a3b605fc54bf733127284e0e46adbcd1180532d8d0f06b684525ae
├── dd2f5668f681c59da620c20459eca51d05ac3412e3e37ab24420329452e5fec9
├── manifest.json
└── version

0 directories, 8 files

cat /tmp/camunda/16e3566755e5dc026d1a6bcc28c8edc7a7aa6d068af8a5b9e10478c1dd900682 | jq .

Шифрование

В ситуациях когда необходимо использовать публичный реестр контейнеров для работы с закрытой кодовой базой, skopeo позволяет использовать шифрование:

openssl genrsa -out private_key.pem

openssl rsa -inform pem -outform pem -pubout -in private_key.pem -out public_key.pem
skopeo copy --encryption-key jwe:public_key.pem docker://antonvkrylov/k8s-course:helloPodman docker://antonvkrylov/k8s-course:helloPodman_encrypted

Этот инструмент также можно использовать для изучения структуры образов:

skopeo inspect docker://antonvkrylov/k8s-course:helloPodman
Ответ
{
    "Name": "docker.io/antonvkrylov/k8s-course",
    "Digest": "sha256:fdede2de3187a7bf0b83cc40567f6f9e71dddb5c0f8840b410c2e628c28a8371",
    "RepoTags": [
        "helloPodman",
        "helloPodman_copy"
    ],
    "Created": "2023-07-15T15:55:52.490530483Z",
    "DockerVersion": "",
    "Labels": {
        "io.buildah.version": "1.29.1"
    },
    "Architecture": "amd64",
    "Os": "linux",
    "Layers": [
        "sha256:38a980f2cc8accf69c23deae6743d42a87eb34a54f02396f3fcfd7c2d06e2c5b",
        "sha256:de849f1cfbe60b1c06a1db83a3129ab0ea397c4852b98e3e4300b12ee57ba111",
        "sha256:a7203ca35e75e068651c9907d659adc721dba823441b78639fde66fc988f042f",
        "sha256:b94c8bc1022b7129e7b55a7e9c522f0bb60f798cc25ef0eab20b368a71dc75b2"
    ],
    "LayersData": [
        {
            "MIMEType": "application/vnd.oci.image.layer.v1.tar+gzip",
            "Digest": "sha256:38a980f2cc8accf69c23deae6743d42a87eb34a54f02396f3fcfd7c2d06e2c5b",
            "Size": 42114164,
            "Annotations": null
        },
        {
            "MIMEType": "application/vnd.oci.image.layer.v1.tar+gzip",
            "Digest": "sha256:de849f1cfbe60b1c06a1db83a3129ab0ea397c4852b98e3e4300b12ee57ba111",
            "Size": 13525123,
            "Annotations": null
        },
        {
            "MIMEType": "application/vnd.oci.image.layer.v1.tar+gzip",
            "Digest": "sha256:a7203ca35e75e068651c9907d659adc721dba823441b78639fde66fc988f042f",
            "Size": 187528104,
            "Annotations": null
        },
        {
            "MIMEType": "application/vnd.oci.image.layer.v1.tar+gzip",
            "Digest": "sha256:b94c8bc1022b7129e7b55a7e9c522f0bb60f798cc25ef0eab20b368a71dc75b2",
            "Size": 18163581,
            "Annotations": null
        }
    ],
    "Env": [
        "PATH=/usr/java/openjdk-17/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "JAVA_HOME=/usr/java/openjdk-17",
        "LANG=C.UTF-8",
        "JAVA_VERSION=17.0.2"
    ]
}

Artifacts

Инструктируем Jenkins сохранять сборочные артефакты и останавливать все запущенные контейнеры после прогона конвейера:

    post {
    // Clean after build
    always {
        cleanWs()
    }
    success {
        echo "Build success"
        //archiveArtifacts artifacts: '**/*.tar.gz', fingerprint: true
    }
    failure {
        echo "Build failed"
        sh "podman stop --all"
    }
    aborted {
        echo "Build aborted"
        sh "podman stop --all"
    }
  }
Источник: https://habr.com/ru/articles/748328/


Интересные статьи

Интересные статьи

Docker Desktop – это прекрасный обслуживаемый способ использования Docker в MacOS или Windows, но для нестандартных сценариев он оставляет желать лучшего. И недавно я столкнулся с одним таким с...
Резидент ОЭЗ «Иннополис» компания IVA Technologies разрабатывает телекоммуникационное оборудование и программное обеспечение для унифицированных коммуникаций.В этой статье расскажем, за счёт каких воз...
Прим. Wunder Fund: в этой длииинной статье вы найдете ряд полезных советов по работе с Docker, как общего характера, так и Python-специфичных. Хоть мы и давно используем Docker в работе, про некоторые...
Предисловие.HR-ы в последнее время активизировались и активно долбят своими предложениями. Во-первых, господа, какого рожна лешего моё старое резюме с hh оказалось у вас? Мои данные видимо слили на hu...
Помните, как в начале 2021 года мы планировали дополнительно нанять в штат 600 программистов? Позади три месяца и в нашей команде уже прибавилось +250 крутых разработчиков. ...