Pull to refresh

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

Reading time11 min
Views44K

Темы которые будут затронуты в данной статье:

Docker vs. Podman

  • Установка Podman

  • Компоненты Podman

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

  • Docker Compose

  • Podman REST API

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

Demo-приложение

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

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

  • Dockerfile

Конвейер:

  • Build

  • Push

  • Test

  • Scan

  • Archive

  • Шифрование

Инструкции по установке тестового стенда Jenkins

Docker vs. Podman

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

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

В этой статье речь пойдет об использовании Podman в контексте CI/CD, но перед этим стоит перечислить несколько отличий Podman от Docker:

  • Podman позволяет конфигурировать короткие имена для реестров. В Docker используется docker.io когда вы выбираете короткое имя.

  • Podman может запускать контейнеры и поды и использованием YAML Кубернетес. А также он может генерировать YAML Кубернетес для запущенных контейнеров.

  • Podman поддерживает поды как в Кубернтес и может запускать несколько контейнеров в одном поде.

  • Podman работает как обычная CLI утилита, а Docker необходимо несколько демонов Linux с привилегиями root.

  • Podman может запускать контейнеры в разных пользовательских пространствах имен в отличии от Docker.

  • Podman поддерживает запуск systemd в контейнерах а также многие другие возможности systemd.

  • Podman не нужен для работы контейнеров, а Doсker использует демон Linux, который должен быть запущен всегда.

  • Все настройки по-умолчанию в Podman можно кастомизировать в отличии от Docker.

В то время как Docker использует монолитное приложение для создания, запуска и загрузки образов контейнеров,
разработчики Podman решили разделить функционал между тремя проектами: Podman, Buildah, и Skopeo.
Podman на Windows и macOS уже доступен, а Buildah пока доступна только на Linux, с возможностью установки в WSL2 на Windows.

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

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

Архитектура Containerd:

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

Команды Docker и Podman:

Команды которых нет в Podman:

docker container update
Podman не поддерживает изменение запущенных контейнеров, рекомендуется пересоздавать их заново.
docker plugin
Podman не поддерживает плагины.
docker swarm
Podman не поддерживает Docker Swarm, вместо этого Podman, для оркестрации интегрируется с Кубернетес и использует CRI-O.
docker trust
Эта команда имплементрована как Podman image trust.

Команды которых нет в Docker:

podman container
podman generate
podman healthcheck
podman image
podman init
podman machine
podman mount
podman network exists/prune/reload
podman play
podman pod
podman system
podman unmount
podman unshare
podman untag
podman volume exists

Установка Podman

Пакеты 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:

podman info | grep registries -A 4

registries:
  search:
  - registry.access.redhat.com
  - registry.redhat.io
  - docker.io

Docker Compose

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>

Demo-приложение

В качестве примера будет использоваться тестовый проект на 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:

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

Конвейер непрерывной интеграции

Jenkins с помощью вебхуков (webhook) можно интегрировать с GitLab или Gitea,
таким образом можно организовать автоматический запуск CI конвейера после коммита в репозиторий Git.

Build

Для сборки образа будем использовать Buildah.

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

  • Buildah поддерживает формат Dockerfile, но его цель это низкоуровневый интерфейс для сборки образов контейнеров без Dockerfile.

  • Buildah не нужен демон Linux, с помощью этого инструмента можно создавать образ контейнера внутри другого контейнера.

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

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

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

Основные конфигурационные файлы, которые используются 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 простые функциональные и интеграционные тесты.

Код
  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 {
          /////////
          /// С помощью grep проверяем версию Java в контейнере 
          /////////
          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:

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:

# С помощью команды inspect получаем манифест camunda-bpm-platform
skopeo inspect docker://camunda/camunda-bpm-platform:wildfly-SNAPSHOT

# Далее копируем этот образ из Dockerhub в директорию /tmp/camunda
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

# Манифест Docker будет содержать директивы которые необходимо выполнять для сборки образа:
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:

# Используем приватный ключ для шифрования образа
skopeo copy --encryption-key jwe:public_key.pem docker://antonvkrylov/k8s-course:helloPodman docker://antonvkrylov/k8s-course:helloPodman_encrypted

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"
    }
  }

Инструкции по установке тестового стенда Jenkins

Инструкции

Скачать docker-compose:

curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

Сделать файл исполняемым:

chmod +x /usr/local/bin/docker-compose

Удостовериться, что docker-compose работает:

docker-compose --version

docker-compose version 1.29.2, build 5becea4c

Сохранить файл со следующим содержимым docker-compose.yaml:

version: '2'
services:
  jenkins:
    image: jenkins/jenkins:2.401.2-lts-jdk11
    user: root
    privileged: true
    container_name: jenkins
    user: jenkins
    volumes:
      - ${PWD}/data:/var/jenkins_home
    environment:
      JENKINS_HOST_HOME: "/data/jenkins"
    ports:
      - "8088:8080"
      - "5000:5000"
      - "50000:50000"
mkdir data
chown -R 1000:1000 data

Запустите docker-compose:

docker-compose up

Планируемый результат:

Pulling jenkins (jenkins:2.60.3)...
2.60.3: Pulling from library/jenkins
55cbf04beb70: Pull complete
1607093a898c: Pull complete
9a8ea045c926: Pull complete
d4eee24d4dac: Pull complete
c58988e753d7: Pull complete
794a04897db9: Pull complete
70fcfa476f73: Pull complete
0539c80a02be: Extracting [=========>                                         ]  26.74MB/133.9MB
54fefc6dcf80: Download complete
911bc90e47a8: Download complete

http://<< IP ADDRESS >>:8088

Сгенерированный автоматический пароль можно найти в логе:

Введите пароль:

Дождитесь завершения загрузки плагинов:

Выберите «new item» => pipeline:

В секции «pipeline» нужно будет добавить скрипт для тестового приложения:

Конвейер для тестового приложения:

Код
pipeline {
agent any
stages {
  stage('Cleanup') {
    steps {
      sh 'rm -rf k8s-course'
    }
  }
  stage('Clone sources') {
    steps {
      sh 'git clone https://github.com/randomfx/k8s-course.git'
    }
  }
  stage('Build image') {
    steps {
      dir('k8s-course/podman/helloPodman') {
        sh 'buildah build -f Dockerfile -t antonvkrylov/k8s-course:helloPodman'
      }
    }
  }
  stage('Push image') {
    steps {
      dir('k8s-course/podman/helloPodman') {
        sh 'buildah push antonvkrylov/k8s-course:helloPodman'
      }
    }
  }
  stage('Scan image') {
    steps {
      dir('k8s-course/podman/helloPodman') {
        sh 'trivy image --security-checks vuln --severity CRITICAL antonvkrylov/k8s-course:helloPodman'
      }
    }
  }
  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 {
          /////////
          /// С помощью grep проверяем версию Java в контейнере
          /////////
          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
          """
          }
        }
      }
    }
  }
  stage('Archive') {
    steps {
      sh"""
      skopeo copy docker://antonvkrylov/k8s-course:helloPodman --dest-compress --dest-compress-format gzip docker-archive:./helloPodman.tar.gz
      """
    }
  }
}
  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"
  }
}
}
Tags:
Hubs:
Total votes 63: ↑14 and ↓49-29
Comments54

Articles