Настраивал кластер Kubernetes с балансировщиком и и вдруг пришла в голову мысль, что сейчас не многие знают как собирать пакеты для Linux: rpm и deb. В каком-то смысле это «утерянное искусство». Искусство потому-что сборка линуксовых пакетов с использованием make, configure, Autoconf, m4 и rpmbuild больше похожа на алхимию или черную магию, чем на инженерию.

Spec-файл для программы с давней историей может достигать нескольких тысяч строчек, make каждый использует так как он хочет, поэтому сборочный процесс для такого софта достаточно запутанный, и чтобы его распутать требуются эксперты.

На первый взгляд может показаться, что контейнеризация сделала, yum/dnf и apt не актуальными, но это далеко не так. Пользовательское пространство в самых популярных дистрибутивах по прежнему состоит из тысяч пакетов в формате deb и rpm. Yum и apt, не смотря на свой солидный возраст, по прежнему используются в Docker-файлах для установки зависимостей.

Возможно, когда-нибудь группа энтузиастов сможет разрубить этот узел, переписав менеджер зависимостей на современный лад, как это произошло с Python, но пока что старый инструментарий все еще в ходу, и не плохо было бы научится им пользоваться.


К счастью есть проекты, которые сильно облегчают работу с rpm и deb, хоть и не полностью избавляют от необходимости их использования.


Возьмем допустим тот же Kubernetes. Для того, чтобы скомпилировать его из исходников и упаковать его компоненты в удобный формат, можно использовать  nFPM – инструмент, который упрощает сборку пакетов для Debian и RedHat-подобных систем.

Для того, чтобы установить nFTP на Centos:

echo '[goreleaser]
name=GoReleaser
baseurl=https://repo.goreleaser.com/yum/
enabled=1
gpgcheck=0' | sudo tee /etc/yum.repos.d/goreleaser.repo
sudo yum install nfpm

Для компиляции kubernetes:

git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
build/run.sh make

После сборки в директории _output должны появится скомпилированные бинарные файлы:

ls -lat _output/dockerized/bin/linux/amd64/
total 1017012
drwxr-xr-x. 2 root root      4096 Feb  9 20:29 .
-rwxr-xr-x. 1 root root   1986820 Feb  9 20:29 go-runner
-rwxr-xr-x. 1 root root  69623960 Feb  9 20:29 kube-aggregator
-rwxr-xr-x. 1 root root   1765528 Feb  9 20:29 kube-log-runner
-rwxr-xr-x. 1 root root  76320920 Feb  9 20:29 kubemark
-rwxr-xr-x. 1 root root  66322584 Feb  9 20:29 kube-scheduler
-rwxr-xr-x. 1 root root 117455056 Feb  9 20:29 e2e.test
-rwxr-xr-x. 1 root root  67494040 Feb  9 20:29 kube-proxy
-rwxr-xr-x. 1 root root  57594008 Feb  9 20:29 kubectl
-rwxr-xr-x. 1 root root   9842948 Feb  9 20:29 ginkgo
-rwxr-xr-x. 1 root root  72220824 Feb  9 20:29 apiextensions-apiserver
-rwxr-xr-x. 1 root root  56463512 Feb  9 20:29 kubectl-convert
-rwxr-xr-x. 1 root root   1687704 Feb  9 20:29 mounter
-rwxr-xr-x. 1 root root  94048408 Feb  9 20:29 kube-apiserver
-rwxr-xr-x. 1 root root  71405720 Feb  9 20:29 kubeadm
-rwxr-xr-x. 1 root root 112444584 Feb  9 20:29 e2e_node.test
-rwxr-xr-x. 1 root root  86634648 Feb  9 20:29 kube-controller-manager
-rwxr-xr-x. 1 root root  78037252 Feb  9 20:29 kubelet
drwxr-xr-x. 3 root root      4096 Feb  9 20:29 ..

В данном случае будет работать с kubelet. Копируем бинарь:

cp _output/dockerized/bin/linux/amd64/kubelet .

Конфиг nfpm для kubelet:

cat << 'EOF' > kubelet-nfpm.yaml
name: kubelet
version: v1.33.0-alpha.1.85+69ab91a5c59617
arch: amd64
platform: linux
section: admin
priority: optional
maintainer: Your Name <your.email@example.com>
description: |
  kubelet is the primary "node agent" that runs on each node in a Kubernetes cluster.
  It is responsible for maintaining a set of pods, which are composed of one or more containers,
  on a local system.

vendor: Kubernetes
homepage: https://kubernetes.io
license: Apache 2.0

contents:
  - src: kubelet
    dst: /usr/bin/kubelet

  - src: kubelet.service
    dst: /etc/systemd/system/kubelet.service

  - src: kubelet-config.yaml
    dst: /etc/kubernetes/config.yaml

  - src: ca.crt
    dst: /etc/kubernetes/ca.crt

  - src: kubelet.key
    dst: /etc/kubernetes/kubelet.key

  - src: kubelet.crt
    dst: /etc/kubernetes/kubelet.crt

  - src: kubeconfig.yaml
    dst: /etc/kubernetes/kubeconfig.yaml

depends:
  - systemd
  - conntrack
  - ebtables
  - socat

scripts:
  postinstall: scripts/postinstall.sh
  preremove: scripts/preremove.sh
EOF

В секции depends указаны пакеты необходимые для установки kubelet.


Конфиг kubelet:

cat << 'EOF' > kubelet-config.yaml
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
  anonymous:
    enabled: false
  webhook:
    enabled: true
  x509:
    clientCAFile: "/etc/kubernetes/ca.crt"
authorization:
  mode: Webhook
clusterDomain: "cluster.local"
clusterDNS:
  - "< CLUSTER_DNS_IP_ADDRESS >"
cgroupDriver: systemd
containerRuntimeEndpoint: "unix:///var/run/containerd/containerd.sock"
podCIDR: "SUBNET"
resolvConf: "/etc/resolv.conf"
runtimeRequestTimeout: "15m"
tlsCertFile: " /etc/kubernetes/kubelet.crt"
tlsPrivateKeyFile: " /etc/kubernetes/kubelet.key"
EOF

юнит-файл systemd:

cat << 'EOF' > kubelet.service
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes
After=containerd.service
Requires=containerd.service

[Service]
ExecStart=/usr/bin/kubelet \
  --config=/etc/kubernetes/config.yaml \
  --kubeconfig=/etc/kubernetes/kubeconfig.yaml \
  --register-node=true \
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

kubelet для работы понадобится container runtime. Это может быть, например containerd:

yum install -y containerd && systemctl start containerd

Создаем установочные скрипты:

mkdir scripts
touch scripts/postinstall.sh
touch scripts/preremove.sh

Они нужны для удаления ненужных файлов и работы с сервисом:

postinstall.sh

cat << 'EOF' > scripts/postinstall.sh
#!/bin/sh
echo "Post-install script running..."
systemctl daemon-reload
systemctl enable kubelet
EOF

preremove.sh

cat << 'EOF' > scripts/preremove.sh
#!/bin/sh
echo "Pre-remove script running..."
# Stop the service before uninstallation
systemctl stop kubelet || true
systemctl disable kubelet || true

# Remove systemd unit file
rm -f /usr/lib/systemd/system/kubelet.service

# Remove configuration files
rm -rf /etc/kubelet

rm -rf /etc/kubernetes
rm -rf /var/lib/kubelet
rm -rf /var/log/containers
rm -rf /var/log/pods
rm -rf /etc/systemd/system/kubelet.service

# Reload systemd to reflect changes
systemctl daemon-reload || true
EOF

Вместо openssl, для генерации TLS сертификатов в тестовых целях, можно использовать Go:

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"flag"
	"math/big"
	"os"
	"time"
)

func main() {
	caCommonName := flag.String("ca-cn", "Kubernetes CA", "Common Name for CA certificate")
	caOrganization := flag.String("ca-org", "My Organization", "Organization for CA certificate")
	kubeletCommonName := flag.String("kubelet-cn", "system:node:kubelet", "Common Name for kubelet certificate")
	kubeletOrganization := flag.String("kubelet-org", "My Organization", "Organization for kubelet certificate")

	flag.Parse()

	caPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		panic(err)
	}

	caTemplate := x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject: pkix.Name{
			Organization: []string{*caOrganization},
			CommonName:   *caCommonName,
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(10, 0, 0), // Valid for 10 years
		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
		BasicConstraintsValid: true,
		IsCA:                  true,
	}

	caCertDER, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caPrivateKey.PublicKey, caPrivateKey)
	if err != nil {
		panic(err)
	}

	caCertPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: caCertDER,
	})

	if err := os.WriteFile("ca.crt", caCertPEM, 0644); err != nil {
		panic(err)
	}

	kubeletPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		panic(err)
	}

	kubeletTemplate := x509.Certificate{
		SerialNumber: big.NewInt(2),
		Subject: pkix.Name{
			Organization: []string{*kubeletOrganization},
			CommonName:   *kubeletCommonName,
		},
		NotBefore:   time.Now(),
		NotAfter:    time.Now().AddDate(1, 0, 0),
		KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
	}

	kubeletCertDER, err := x509.CreateCertificate(rand.Reader, &kubeletTemplate, &caTemplate, &kubeletPrivateKey.PublicKey, caPrivateKey)
	if err != nil {
		panic(err)
	}

	kubeletCertPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: kubeletCertDER,
	})

	if err := os.WriteFile("kubelet.crt", kubeletCertPEM, 0644); err != nil {
		panic(err)
	}

	kubeletPrivateKeyPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(kubeletPrivateKey),
	})

	if err := os.WriteFile("kubelet.key", kubeletPrivateKeyPEM, 0600); err != nil {
		panic(err)
	}

	println("CA and kubelet certificates generated successfully")
}

Запускаем программу:

go run main.go --ca-cn="Custom CA" --ca-org="Custom Org" --kubelet-cn="Custom Node" --kubelet-org="Custom Node Org"

Сгенерированные сертификаты:

ls
ca.crt       main.go
kubelet.crt  systemd-private-ea88d408c3414587889b6c620cb18b56-chronyd.service-x05vNn
kubelet.key  systemd-private-ea88d408c3414587889b6c620cb18b56-dbus-broker.service-3sYWhs
kubernetes   systemd-private-ea88d408c3414587889b6c620cb18b56-systemd-logind.service-Z1XmLV

kubeconfig для того, чтобы kubelet знал как подключится к kube-apiserver:

cat << 'EOF' > kubeconfig
apiVersion: v1
kind: Config
clusters:
- name: kubernetes
  cluster:
    certificate-authority: ca.crt
    server: https://your-kubernetes-api-server:6443

contexts:
- name: kubelet-context
  context:
    cluster: kubernetes
    user: kubelet-user

users:
- name: kubelet-user
  user:
    client-certificate: kubelet.crt
    client-key: kubelet.key

current-context: kubelet-context
EOF

Для сборки пакета:

nfpm pkg --packager rpm --config ./kubelet-nfpm.yaml

Устанавливаем пакет:

yum install ./kubelet-1.33.0~alpha.1.85+69ab91a5c59617-1.x86_64.rpm

Проверяем установку:

yum info kubelet
Last metadata expiration check: 0:04:25 ago on Mon Feb 10 03:11:44 2025.
Installed Packages
Name         : kubelet
Version      : 1.33.0~alpha.1.85+69ab91a5c59617
Release      : 1
Architecture : x86_64
Size         : 74 M
Source       : kubelet-1.33.0~alpha.1.85+69ab91a5c59617-1.src.rpm
Repository   : @System
From repo    : @commandline
Summary      : kubelet is the primary "node agent" that runs on each node in a Kubernetes
             : cluster.
URL          : https://kubernetes.io
License      : Apache 2.0
Description  : kubelet is the primary "node agent" that runs on each node in a Kubernetes
             : cluster. It is responsible for maintaining a set of pods, which are composed of
             : one or more containers, on a local system.

keepalived – это хорошо известный инструмент для балансировки и обеспечения отказоустойчивости. Это древняя программка написанная на c, которая продолжает оставаться незаменимой в арсенале DevOps инженеров.

Для того, чтобы скачать исходники и скомпилировать код:

curl https://keepalived.org/software/keepalived-2.3.2.tar.gz | tar xz
cd keepalived-2.3.2
./configure --enable-json
make
make install

Проверим версию скомпилированного бинарного файла:

./bin/keepalived -v
Keepalived v2.3.2 (11/03,2024)

Copyright(C) 2001-2024 Alexandre Cassen, <acassen@gmail.com>

Built with kernel headers for Linux 6.8.12
Running on Linux 6.8.0-52-generic #53-Ubuntu SMP PREEMPT_DYNAMIC Sat Jan 11 00:06:25 UTC 2025
Distro: Ubuntu 24.04.1 LTS

Пример кастомизации сборки - добавим поддержку json с помощью флага --enable-json:

configure
configure

Создаем конфиг nfpm:

cat << 'EOF' > nfmp.yaml
name: keepalived
version: 2.3.2
arch: amd64
platform: linux
section: admin
priority: optional
maintainer: Your Name <your.email@example.com>
description: |
  Keepalived is a routing software written in C. The main goal of this project is to provide simple and robust facilities for loadbalancing and high-availability to Linux system and Linux based infrastructures.
vendor: Your Organization
homepage: https://www.keepalived.org/
license: GPL-2.0

contents:
  - src: bin/keepalived
    dst: /usr/sbin/keepalived

  - src: keepalived.conf
    dst: /etc/keepalived/keepalived.conf

  - src: keepalived.service
    dst: /usr/lib/systemd/system/keepalived.service

  - dst: /var/log/keepalived
    type: dir

conflicts:
  - keepalived-legacy

replaces:
  - keepalived-legacy

scripts:
  postinstall: ./scripts/postinstall.sh
  preremove: ./scripts/preremove.sh
EOF

При желанию можно подписать пакет с помощью gpg ключа:

signature:
  key_file: key.gpg

Конфиг keepalived можно сделать частью установочного пакета, если в этом есть необходимость:

cat << 'EOF' > keepalived.conf
global_defs {
  enable_script_security
  script_user root
}

vrrp_script chk_haproxy {
    script 'killall -0 haproxy' # faster than pidof
    interval 2
}

vrrp_instance haproxy-vip {
    interface eth0
    state BACKUP # MASTER on lb-1, BACKUP on lb-2
    priority 100 # 200 on lb-1, 100 on lb-2

    virtual_router_id 51

    virtual_ipaddress {
        10.10.10.100/24
    }

    track_script {
        chk_haproxy
    }
}
EOF

Keepalived будет работать как демон под управлением SystemD, поэтому нам понадобится юнит-файл:

cat << 'EOF' > keepalived.service
[Unit]
Description=LVS and VRRP High Availability Monitor
After=network-online.target syslog.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/var/run/keepalived.pid
KillMode=process
EnvironmentFile=-/etc/sysconfig/keepalived
ExecStart=/usr/sbin/keepalived $KEEPALIVED_OPTIONS
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target
EOF

После установки и перед удалением необходимо будет запустить сервис SystemD и удалить конфигурационные файлы поэтому добавляем установочные скрипты:

cat << 'EOF' > scripts/preremove.sh
#!/bin/sh
echo "Pre-remove script running..."
# Stop the service before uninstallation
systemctl stop keepalived || true
systemctl disable keepalived || true

# Remove systemd unit file
rm -f /usr/lib/systemd/system/keepalived.service

# Remove configuration files
rm -rf /etc/keepalived

# Reload systemd to reflect changes
systemctl daemon-reload || true
EOF
cat << 'EOF' > scripts/postinstall.sh
#!/bin/sh
echo "Post-install script running..."
systemctl daemon-reload
systemctl enable keepalived
EOF

Собираем и устанавливаем пакет:

nfpm pkg --packager --config nfpm.yaml deb --target /tmp/
dpkg -i /tmp/keepalived-2.3.2-1.x86_64.deb

Комментарии (0)