Серверное оборудование для бизнеса — надёжные решения для любых задач Подробнее →
Статья

Makefile для DevOps: автоматизация рутины

Makefile для DevOps: автоматизация рутины

В каждом проекте со временем накапливается набор команд, которые нужно запускать регулярно: собрать образ, запустить тесты, задеплоить в staging, почистить старые контейнеры. Эти команды оседают в README, в головах инженеров, в случайных скриптах. Makefile — простой и проверенный временем способ собрать всё в одном месте и сделать команды самодокументируемыми.

Почему Makefile, а не shell-скрипты

Shell-скрипты хороши, но у них есть проблемы: нет стандартного способа перечислить доступные команды, нет встроенных зависимостей между задачами, сложно передавать параметры. Makefile решает всё это из коробки. Плюс make установлен практически на каждом Linux/macOS-сервере без дополнительных зависимостей.

Базовый синтаксис

# Переменные
IMAGE_NAME := my-app
TAG        := $(shell git rev-parse --short HEAD)
REGISTRY   := registry.example.com

# Цель: имя
# <TAB> команда  (именно TAB, не пробелы!)
build:
    docker build -t $(IMAGE_NAME):$(TAG) .

push: build
    docker tag $(IMAGE_NAME):$(TAG) $(REGISTRY)/$(IMAGE_NAME):$(TAG)
    docker push $(REGISTRY)/$(IMAGE_NAME):$(TAG)

.PHONY: build push

Обратите внимание: отступ перед командами — это обязательно символ TAB, не пробелы. Это частая ошибка новичков.

Практичный DevOps Makefile

.DEFAULT_GOAL := help
COMPOSE_FILE  := docker-compose.yml
ENV           ?= dev

help:  ## Показать список команд
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | 
      awk 'BEGIN {FS = ":.*?## "}; {printf "33[36m%-20s33[0m %s
", $$1, $$2}'

up: ## Запустить окружение
    docker compose -f $(COMPOSE_FILE) up -d

down: ## Остановить окружение
    docker compose -f $(COMPOSE_FILE) down

logs: ## Показать логи
    docker compose -f $(COMPOSE_FILE) logs -f

build: ## Собрать образы
    docker compose -f $(COMPOSE_FILE) build --no-cache

test: ## Запустить тесты
    docker compose run --rm app pytest -v

migrate: ## Применить миграции БД
    docker compose run --rm app python manage.py migrate

lint: ## Проверить код
    docker compose run --rm app flake8 .

clean: ## Удалить остановленные контейнеры и тома
    docker compose down -v --remove-orphans
    docker image prune -f

deploy: build push ## Собрать и задеплоить
    helm upgrade --install my-app ./chart 
      --set image.tag=$(TAG) 
      --namespace $(ENV)

.PHONY: help up down logs build test migrate lint clean deploy

Трюк с help

Строки с ## комментарий после цели автоматически попадают в вывод make help. Коллеги запускают make help и сразу видят все доступные команды с описаниями — никакого README не нужно.

Условия и параметры

# Передать переменную при запуске: make deploy ENV=production
ENV ?= staging

deploy:
ifeq ($(ENV),production)
    @echo "Деплой в PRODUCTION, подтвердите: нажмите Ctrl+C для отмены"
    @sleep 5
endif
    helm upgrade --install my-app ./chart --namespace $(ENV)

Зависимости между целями

release: lint test build push deploy ## Полный цикл релиза

Запуск make release выполнит все цели по порядку. Если любая из них завершится с ошибкой — цепочка остановится. Это встроенное поведение Make.

Включение внешних файлов

Для больших проектов можно разбить Makefile на части:

include makefiles/docker.mk
include makefiles/k8s.mk
include makefiles/db.mk

Параллельное выполнение

make -j4 lint test build  # запустить три цели параллельно

Полезно для независимых задач — например, одновременный запуск линтеров для разных языков.

Makefile не пытается быть универсальным инструментом сборки. Его сила — в простоте и универсальности. Добавьте Makefile в каждый свой проект, и через месяц вы не сможете понять, как работали без него.

3 Ответа

  1. Трюк с grep для автогенерации help — просто огонь, сразу добавил к себе в шаблон. Теперь все новые проекты начинаю именно с такого Makefile.

  1. Про TAB обязательно надо предупреждать, потерял час однажды из-за того что редактор автоматом заменил на пробелы. Лучше сразу настроить .editorconfig.

  1. Используем похожий подход, только ещё добавили цель check-env которая проверяет что все нужные переменные окружения заданы перед деплоем.