From 7b70d3d9394dc17b74eb9baf939e36819cc79c17 Mon Sep 17 00:00:00 2001 From: Julien Duchesne Date: Mon, 1 Aug 2022 09:22:51 -0400 Subject: [PATCH] deb and rpm packaging for all binaries (#6456) * deb and rpm packaging for all binaries There is currently deb and rpm packages being published for logcli amd64 but they aren't signed In this PR: - Add RPM and deb packages for `logcli`, `loki-canary` `loki` and `promtail` for arm, arm64 and amd64 - Sign them with the Grafana GPG key (packages.grafana.com/gpg.key). This will be the same key for all Grafana products - Add CI steps to test the RPM and deb packages. This launches a docker image with systemd installed, then it installs both loki and promtail and does a `logcli` query on the Loki instance * Remove specific version in test scripts * Bump build image to 0.22.0 * Add restart policy for promtail's service --- .drone/drone.jsonnet | 100 +++++++++++++++++++++--- .drone/drone.yml | 69 ++++++++++++++-- Makefile | 6 +- tools/nfpm.yaml | 18 ----- tools/packaging/loki-postinstall.sh | 62 +++++++++++++++ tools/packaging/loki.service | 15 ++++ tools/packaging/nfpm.jsonnet | 90 +++++++++++++++++++++ tools/packaging/nfpm.sh | 24 ++++++ tools/packaging/promtail-postinstall.sh | 62 +++++++++++++++ tools/packaging/promtail.service | 15 ++++ tools/packaging/verify-deb-install.sh | 34 ++++++++ tools/packaging/verify-rpm-install.sh | 37 +++++++++ 12 files changed, 493 insertions(+), 39 deletions(-) delete mode 100644 tools/nfpm.yaml create mode 100644 tools/packaging/loki-postinstall.sh create mode 100644 tools/packaging/loki.service create mode 100644 tools/packaging/nfpm.jsonnet create mode 100755 tools/packaging/nfpm.sh create mode 100644 tools/packaging/promtail-postinstall.sh create mode 100644 tools/packaging/promtail.service create mode 100755 tools/packaging/verify-deb-install.sh create mode 100755 tools/packaging/verify-rpm-install.sh diff --git a/.drone/drone.jsonnet b/.drone/drone.jsonnet index 82c8779d55..c4b67e88ee 100644 --- a/.drone/drone.jsonnet +++ b/.drone/drone.jsonnet @@ -39,6 +39,8 @@ local ecr_key = secret('ecr_key', 'infra/data/ci/loki/aws-credentials', 'access_ local ecr_secret_key = secret('ecr_secret_key', 'infra/data/ci/loki/aws-credentials', 'secret_access_key'); local pull_secret = secret('dockerconfigjson', 'secret/data/common/gcr', '.dockerconfigjson'); local github_secret = secret('github_token', 'infra/data/ci/github/grafanabot', 'pat'); +local gpg_passphrase = secret('gpg_passphrase', 'infra/data/ci/packages-publish/gpg', 'passphrase'); +local gpg_private_key = secret('gpg_private_key', 'infra/data/ci/packages-publish/gpg', 'private-key'); // Injected in a secret because this is a public repository and having the config here would leak our environment names local deploy_configuration = secret('deploy_config', 'secret/data/common/loki_ci_autodeploy', 'config.json'); @@ -580,18 +582,92 @@ local manifest_ecr(apps, archs) = pipeline('manifest-ecr') { event: ['pull_request', 'tag'], }, image_pull_secrets: [pull_secret.name], + volumes+: [ + { + name: 'cgroup', + host: { + path: '/sys/fs/cgroup', + }, + }, + { + name: 'docker', + host: { + path: '/var/run/docker.sock', + }, + }, + ], + // Launch docker images with systemd + services: [ + { + name: 'systemd-debian', + image: 'jrei/systemd-debian:12', + volumes: [ + { + name: 'cgroup', + path: '/sys/fs/cgroup', + }, + ], + privileged: true, + }, + { + name: 'systemd-centos', + image: 'jrei/systemd-centos:8', + volumes: [ + { + name: 'cgroup', + path: '/sys/fs/cgroup', + }, + ], + privileged: true, + }, + ], + // Package and test the packages steps: [ - run( - 'test packaging', - commands=['make BUILD_IN_CONTAINER=false packages'] - ) { when: { event: ['pull_request'] } }, - run( - 'publish', - commands=['make BUILD_IN_CONTAINER=false publish'], - env={ - GITHUB_TOKEN: { from_secret: github_secret.name }, - } - ) { when: { event: ['tag'] } }, + run('write-key', + commands=['printf "%s" "$NFPM_SIGNING_KEY" > $NFPM_SIGNING_KEY_FILE'], + env={ + NFPM_SIGNING_KEY: { from_secret: gpg_private_key.name }, + NFPM_SIGNING_KEY_FILE: '/drone/src/private-key.key', + }), + run('test packaging', + commands=[ + 'make BUILD_IN_CONTAINER=false packages', + ], + env={ + NFPM_PASSPHRASE: { from_secret: gpg_passphrase.name }, + NFPM_SIGNING_KEY_FILE: '/drone/src/private-key.key', + }), + { + name: 'test deb package', + image: 'docker', + commands: ['./tools/packaging/verify-deb-install.sh'], + volumes: [ + { + name: 'docker', + path: '/var/run/docker.sock', + }, + ], + privileged: true, + }, + { + name: 'test rpm package', + image: 'docker', + commands: ['./tools/packaging/verify-rpm-install.sh'], + volumes: [ + { + name: 'docker', + path: '/var/run/docker.sock', + }, + ], + privileged: true, + }, + run('publish', + commands=['make BUILD_IN_CONTAINER=false publish'], + env={ + GITHUB_TOKEN: { from_secret: github_secret.name }, + NFPM_PASSPHRASE: { from_secret: gpg_passphrase.name }, + NFPM_SIGNING_KEY_FILE: '/drone/src/private-key.key', + }) { when: { event: ['tag'] } }, ], }, ] @@ -610,4 +686,6 @@ local manifest_ecr(apps, archs) = pipeline('manifest-ecr') { ecr_key, ecr_secret_key, deploy_configuration, + gpg_passphrase, + gpg_private_key, ] diff --git a/.drone/drone.yml b/.drone/drone.yml index 8b737b0fe7..b6e3b92a9f 100644 --- a/.drone/drone.yml +++ b/.drone/drone.yml @@ -1183,20 +1183,60 @@ image_pull_secrets: - dockerconfigjson kind: pipeline name: release +services: +- image: jrei/systemd-debian:12 + name: systemd-debian + privileged: true + volumes: + - name: cgroup + path: /sys/fs/cgroup +- image: jrei/systemd-centos:8 + name: systemd-centos + privileged: true + volumes: + - name: cgroup + path: /sys/fs/cgroup steps: +- commands: + - printf "%s" "$NFPM_SIGNING_KEY" > $NFPM_SIGNING_KEY_FILE + environment: + NFPM_SIGNING_KEY: + from_secret: gpg_private_key + NFPM_SIGNING_KEY_FILE: /drone/src/private-key.key + image: grafana/loki-build-image:0.22.0 + name: write-key - commands: - make BUILD_IN_CONTAINER=false packages - environment: {} + environment: + NFPM_PASSPHRASE: + from_secret: gpg_passphrase + NFPM_SIGNING_KEY_FILE: /drone/src/private-key.key image: grafana/loki-build-image:0.22.0 name: test packaging - when: - event: - - pull_request +- commands: + - ./tools/packaging/verify-deb-install.sh + image: docker + name: test deb package + privileged: true + volumes: + - name: docker + path: /var/run/docker.sock +- commands: + - ./tools/packaging/verify-rpm-install.sh + image: docker + name: test rpm package + privileged: true + volumes: + - name: docker + path: /var/run/docker.sock - commands: - make BUILD_IN_CONTAINER=false publish environment: GITHUB_TOKEN: from_secret: github_token + NFPM_PASSPHRASE: + from_secret: gpg_passphrase + NFPM_SIGNING_KEY_FILE: /drone/src/private-key.key image: grafana/loki-build-image:0.22.0 name: publish when: @@ -1211,6 +1251,13 @@ trigger: - refs/heads/k??? - refs/tags/v* - refs/pull/*/head +volumes: +- host: + path: /sys/fs/cgroup + name: cgroup +- host: + path: /var/run/docker.sock + name: docker --- depends_on: - check @@ -1416,7 +1463,19 @@ get: kind: secret name: deploy_config --- +get: + name: passphrase + path: infra/data/ci/packages-publish/gpg +kind: secret +name: gpg_passphrase +--- +get: + name: private-key + path: infra/data/ci/packages-publish/gpg +kind: secret +name: gpg_private_key +--- kind: signature -hmac: 4fa63210e8bdd946f1732051b6b841eba0ae1b0313436301c743341d29893c8f +hmac: 5211ba463ba49d82d8930928f2ea05232999d07d37f0a51dc8b535bfcd10727d ... diff --git a/Makefile b/Makefile index 8f794c4297..0171fd6148 100644 --- a/Makefile +++ b/Makefile @@ -263,11 +263,7 @@ dist: clean pushd dist && sha256sum * > SHA256SUMS && popd packages: dist - mkdir -p dist/tmp - unzip dist/logcli-linux-amd64.zip -d dist/tmp - nfpm package -f tools/nfpm.yaml -p rpm -t dist/ - nfpm package -f tools/nfpm.yaml -p deb -t dist/ - rm -rf dist/tmp + @tools/packaging/nfpm.sh publish: packages ./tools/release diff --git a/tools/nfpm.yaml b/tools/nfpm.yaml deleted file mode 100644 index 4d3ed3d93f..0000000000 --- a/tools/nfpm.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: "logcli" -arch: "amd64" -platform: "linux" -version: ${DRONE_TAG} -section: "default" -provides: - - logcli -maintainer: "Grafana Labs " -description: | - LogCLI is the command-line interface to Loki. - It facilitates running LogQL queries against a Loki instance. -vendor: "Grafana Labs Inc" -homepage: "https://grafana.com/loki" -license: "AGPL-3.0" -contents: - - src: ./dist/tmp/logcli-linux-amd64 - dst: /usr/local/bin/logcli diff --git a/tools/packaging/loki-postinstall.sh b/tools/packaging/loki-postinstall.sh new file mode 100644 index 0000000000..acf8df4bcc --- /dev/null +++ b/tools/packaging/loki-postinstall.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Based on https://nfpm.goreleaser.com/tips/ + +if ! command -V systemctl >/dev/null 2>&1; then + echo "Could not find systemd. Skipping system installation." && exit 0 +else + systemd_version=$(systemctl --version | head -1 | sed 's/systemd //g') +fi + +cleanInstall() { + printf "\033[32m Post Install of a clean install\033[0m\n" + + # Create the user + if ! id loki > /dev/null 2>&1 ; then + adduser --system --shell /bin/false "loki" + fi + + # rhel/centos7 cannot use ExecStartPre=+ to specify the pre start should be run as root + # even if you want your service to run as non root. + if [ "${systemd_version}" -lt 231 ]; then + printf "\033[31m systemd version %s is less then 231, fixing the service file \033[0m\n" "${systemd_version}" + sed -i "s/=+/=/g" /etc/systemd/system/loki.service + fi + printf "\033[32m Reload the service unit from disk\033[0m\n" + systemctl daemon-reload ||: + printf "\033[32m Unmask the service\033[0m\n" + systemctl unmask loki ||: + printf "\033[32m Set the preset flag for the service unit\033[0m\n" + systemctl preset loki ||: + printf "\033[32m Set the enabled flag for the service unit\033[0m\n" + systemctl enable loki ||: + systemctl restart loki ||: +} + +upgrade() { + : + # printf "\033[32m Post Install of an upgrade\033[0m\n" +} + +action="$1" +if [ "$1" = "configure" ] && [ -z "$2" ]; then + # Alpine linux does not pass args, and deb passes $1=configure + action="install" +elif [ "$1" = "configure" ] && [ -n "$2" ]; then + # deb passes $1=configure $2= + action="upgrade" +fi + +case "${action}" in + "1" | "install") + cleanInstall + ;; + "2" | "upgrade") + upgrade + ;; + *) + # $1 == version being installed + printf "\033[32m Alpine\033[0m" + cleanInstall + ;; +esac diff --git a/tools/packaging/loki.service b/tools/packaging/loki.service new file mode 100644 index 0000000000..b0abbb7191 --- /dev/null +++ b/tools/packaging/loki.service @@ -0,0 +1,15 @@ +[Unit] +Description=Loki service +After=network.target + +[Service] +Type=simple +User=loki +ExecStart=/usr/bin/loki -config.file /etc/loki/config.yml +# Give a reasonable amount of time for the server to start up/shut down +TimeoutSec = 120 +Restart = on-failure +RestartSec = 2 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/tools/packaging/nfpm.jsonnet b/tools/packaging/nfpm.jsonnet new file mode 100644 index 0000000000..b313cf0e48 --- /dev/null +++ b/tools/packaging/nfpm.jsonnet @@ -0,0 +1,90 @@ +local overrides = { + logcli: { + description: + ||| + LogCLI is the command-line interface to Loki. + It facilitates running LogQL queries against a Loki instance. + |||, + }, + + 'loki-canary': { + description: 'Loki Canary is a standalone app that audits the log-capturing performance of a Grafana Loki cluster.', + }, + + loki: { + description: ||| + Loki is a horizontally-scalable, highly-available, multi-tenant log aggregation system inspired by Prometheus. + It is designed to be very cost effective and easy to operate. + It does not index the contents of the logs, but rather a set of labels for each log stream. + |||, + contents+: [ + { + src: './tools/packaging/loki.service', + dst: '/etc/systemd/system/loki.service', + }, + { + src: './cmd/loki/loki-local-config.yaml', + dst: '/etc/loki/config.yml', + type: 'config|noreplace', + }, + ], + scripts: { + postinstall: './tools/packaging/loki-postinstall.sh', + }, + }, + + promtail: { + description: ||| + Promtail is an agent which ships the contents of local logs to a private Grafana Loki instance or Grafana Cloud. + It is usually deployed to every machine that has applications needed to be monitored. + |||, + license: 'Apache-2.0', + contents+: [ + { + src: './tools/packaging/promtail.service', + dst: '/etc/systemd/system/promtail.service', + }, + { + src: './clients/cmd/promtail/promtail-local-config.yaml', + dst: '/etc/promtail/config.yml', + type: 'config|noreplace', + }, + ], + scripts: { + postinstall: './tools/packaging/promtail-postinstall.sh', + }, + }, +}; + +local name = std.extVar('name'); +local arch = std.extVar('arch'); + +{ + name: name, + arch: arch, + platform: 'linux', + version: '${DRONE_TAG}', + section: 'default', + provides: [name], + maintainer: 'Grafana Labs ', + vendor: 'Grafana Labs Inc', + homepage: 'https://grafana.com/loki', + license: 'AGPL-3.0', + contents: [{ + src: './dist/tmp/packages/%s-linux-%s' % [name, arch], + dst: '/usr/bin/%s' % name, + }], + + deb: { + signature: { + // Also set ${NFPM_PASSPHRASE} + key_file: '${NFPM_SIGNING_KEY_FILE}', + }, + }, + rpm: { + signature: { + // Also set ${NFPM_PASSPHRASE} + key_file: '${NFPM_SIGNING_KEY_FILE}', + }, + }, +} + overrides[name] diff --git a/tools/packaging/nfpm.sh b/tools/packaging/nfpm.sh new file mode 100755 index 0000000000..899bfd75fe --- /dev/null +++ b/tools/packaging/nfpm.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +if [[ -z "${NFPM_SIGNING_KEY_FILE}" ]]; then + echo "NFPM_SIGNING_KEY_FILE is not set" + exit 1 +fi +if [[ -z "${NFPM_PASSPHRASE}" ]]; then + echo "NFPM_PASSPHRASE is not set" + exit 1 +fi + +rm -rf dist/tmp && mkdir -p dist/tmp/packages +unzip dist/\*.zip -d dist/tmp/packages + +for name in loki loki-canary logcli promtail; do + for arch in amd64 arm64 arm; do + config_path="dist/tmp/config-${name}-${arch}.json" + jsonnet -V "name=${name}" -V "arch=${arch}" "tools/packaging/nfpm.jsonnet" > "${config_path}" + nfpm package -f "${config_path}" -p rpm -t dist/ + nfpm package -f "${config_path}" -p deb -t dist/ + done +done + +rm -rf dist/tmp \ No newline at end of file diff --git a/tools/packaging/promtail-postinstall.sh b/tools/packaging/promtail-postinstall.sh new file mode 100644 index 0000000000..b4c964388f --- /dev/null +++ b/tools/packaging/promtail-postinstall.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Based on https://nfpm.goreleaser.com/tips/ + +if ! command -V systemctl >/dev/null 2>&1; then + echo "Could not find systemd. Skipping system installation." && exit 0 +else + systemd_version=$(systemctl --version | head -1 | sed 's/systemd //g') +fi + +cleanInstall() { + printf "\033[32m Post Install of a clean install\033[0m\n" + + # Create the user + if ! id promtail > /dev/null 2>&1 ; then + adduser --system --shell /bin/false "promtail" + fi + + # rhel/centos7 cannot use ExecStartPre=+ to specify the pre start should be run as root + # even if you want your service to run as non root. + if [ "${systemd_version}" -lt 231 ]; then + printf "\033[31m systemd version %s is less then 231, fixing the service file \033[0m\n" "${systemd_version}" + sed -i "s/=+/=/g" /etc/systemd/system/promtail.service + fi + printf "\033[32m Reload the service unit from disk\033[0m\n" + systemctl daemon-reload ||: + printf "\033[32m Unmask the service\033[0m\n" + systemctl unmask promtail ||: + printf "\033[32m Set the preset flag for the service unit\033[0m\n" + systemctl preset promtail ||: + printf "\033[32m Set the enabled flag for the service unit\033[0m\n" + systemctl enable promtail ||: + systemctl restart promtail ||: +} + +upgrade() { + : + # printf "\033[32m Post Install of an upgrade\033[0m\n" +} + +action="$1" +if [ "$1" = "configure" ] && [ -z "$2" ]; then + # Alpine linux does not pass args, and deb passes $1=configure + action="install" +elif [ "$1" = "configure" ] && [ -n "$2" ]; then + # deb passes $1=configure $2= + action="upgrade" +fi + +case "${action}" in + "1" | "install") + cleanInstall + ;; + "2" | "upgrade") + upgrade + ;; + *) + # $1 == version being installed + printf "\033[32m Alpine\033[0m" + cleanInstall + ;; +esac diff --git a/tools/packaging/promtail.service b/tools/packaging/promtail.service new file mode 100644 index 0000000000..a21768d110 --- /dev/null +++ b/tools/packaging/promtail.service @@ -0,0 +1,15 @@ +[Unit] +Description=Promtail service +After=network.target + +[Service] +Type=simple +User=promtail +ExecStart=/usr/bin/promtail -config.file /etc/promtail/config.yml +# Give a reasonable amount of time for promtail to start up/shut down +TimeoutSec = 60 +Restart = on-failure +RestartSec = 2 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/tools/packaging/verify-deb-install.sh b/tools/packaging/verify-deb-install.sh new file mode 100755 index 0000000000..c9d75e8c89 --- /dev/null +++ b/tools/packaging/verify-deb-install.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +docker ps +image="$(docker ps --filter ancestor=jrei/systemd-debian:12 --latest --format "{{.ID}}")" +echo "Running on container: ${image}" + +dir="." +if [ -n "${CI}" ]; then + dir="/drone/src" +fi +echo "Running on directory: ${dir}" + +cat < /var/log/test.log + + # Install logcli + dpkg -i ${dir}/dist/logcli_*_amd64.deb + + # Check that there are labels + sleep 5 + labels_found=\$(logcli labels) + echo "Found labels: \$labels_found" + [ "\$labels_found" != "" ] || (echo "no logs found with logcli" && exit 1) +EOF \ No newline at end of file diff --git a/tools/packaging/verify-rpm-install.sh b/tools/packaging/verify-rpm-install.sh new file mode 100755 index 0000000000..93356ca736 --- /dev/null +++ b/tools/packaging/verify-rpm-install.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +docker ps +image="$(docker ps --filter ancestor=jrei/systemd-centos:8 --latest --format "{{.ID}}")" +echo "Running on container: ${image}" + +dir="." +if [ -n "${CI}" ]; then + dir="/drone/src" +fi +echo "Running on directory: ${dir}" + +cat < /var/log/test.log + + # Install logcli + rpm -i ${dir}/dist/logcli-*.x86_64.rpm + + # Check that there are labels + sleep 5 + labels_found=\$(logcli labels) + echo "Found labels: \$labels_found" + [ "\$labels_found" != "" ] || (echo "no labels found with logcli" && exit 1) +EOF \ No newline at end of file