Привет, Хабр!
Понимаете ли вы пользу документации? Конечно да. Наверняка каждый встречался с ужасно или вовсе не задокументированным проектом. Это всегда больно и с языка так и срываются матерные слова. А бывает, что документация вроде и есть, но разбросана по корпоративному вики, по разным репозиториям, разным директориям. В общем мрак.
В этой статье хотелось бы поговорить про конкретную область для документирования — микросервисы и REST API. Не буду ходить вокруг да около, конечно же всем известна спецификация OpenAPI, пережившая уже целых три поколения и Swagger, де-факто ставший стандартом индустрии. Речь пойдёт о том, как сделать систему с автоматически актуализируемой Swagger-документацией через использование CI/CD пайплайна и парочки полезных утилит.
Предисловие
Примеры и рассуждения будут приведены для приложения на языке Go, но всё то же самое актуально и для других языков, которые поддерживают подобную генерацию документации.
О чём, собственно, речь
А речь о проекте swaggo/swag, который позволяет описывать документацию в формате Swagger через javadoc-like комментарии к хэндлерам. Самая простая интеграция у проекта с фреймворком gin, но у меня не возникало особых проблем и при использовании echo, и даже «голого» net/http.
Что нужно знать про swag
Итак, немного углубимся в тему. Представим, что у нас есть какое-то несложное (или сложное) веб-приложение. Пусть написали мы его на gin. Значит, в приложении однозначно будет хотя бы один handler, например такой:
func HelloWorld(g *gin.Context) { g.JSON(http.StatusOK, "helloworld") }
И вот вы захотели для этого жутко сложного метода получить Swagger-документацию. Самый простой путь, что называется «в лоб» — пойти и написать OpenAPI v2 спецификацию вручную, получится что-то вроде такого:
swagger: "2.0" info: title: My API description: My API license: name: MIT version: 0.0.1 paths: /: get: summary: Ping description: Is used to test connection. responses: '200': description: Success
В общем-то незамысловато и так вполне можно поступить, если бы только все приложения были такими маленькими и никогда не расширялись. Каждое изменение в хэндлерах придётся документировать дважды — в комментариях к коду и в этом yaml (или json, если БДСМ сердцу ближе). Но можно этого избежать, примерно вот так:
// @Summary Ping // @Description Is used to test connection. // @Accept json // @Produce json // @Success 200 {string} helloworld // @Router / [get] // // I'm vanila godoc func HelloWorld(g *gin.Context) { g.JSON(http.StatusOK, "helloworld") }
Стало чуть приятнее, не правда ли? Теперь и код сам по себе задокументирован, и никаких отдельных файлов городить не нужно. С помощью таких же комментариев можно описать все остальные хэндлеры, если они есть, а «шапка», то есть блок info, описывается прямо над функцией main — точкой входа в приложение. За подробностями отсылаю к документации, где прекрасно описаны все имеющиеся на данный момент аннотации и дополнительные фишки, такие как описание структуры тела запроса в виде структуры в Go.
После того, как комментарии оставлены, их нужно как-то собрать воедино и превратить это в то, что выше было описано руками. Для этого потребуется cli-утилита swag, самый простой способ установить которую — использовать go install (не забудьте добавить директорию с пакетом в PATH, вероятнее всего это будет ~/go/bin):
go install github.com/swaggo/swag/cmd/swag@latest
Генерация выполняется следующим образом:
swag init -d "directories/with,comments" --parseDependency
Вообще говоря, эти флаги не обязательны, но я предпочитаю их указывать во избежание ошибок. Подробнее о них, опять же, написано в документации.
В результате будут сгенерированы три файла, по умолчанию в директории docs/: docs.go, swagger.yaml и swagger.json. Как не трудно догадаться — это и есть наша документация в разных форматах.
Swagger UI
Наличие docs.go намекает на то, что рядом с приложением можно поднять и Swagger UI. Делается это крайне просто (пример с небольшими изменениями взят из документации):
import ( "net/http" docs "module_name/docs" swaggerfiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() docs.SwaggerInfo.BasePath = "/api/v1" v1 := r.Group("/api/v1") { eg := v1.Group("/example") { eg.GET("/helloworld", HelloWorld) } } r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) r.Run(":8080") }
Теперь при запуске приложения нетрудно найти Swagger UI, который находится по адресу /swagger/.
Путь к автоматизации
Теперь вернёмся к начальному тезису этой статьи. Одной документации мало, нужно как-то обеспечить её постоянное обновление. Отсутствующие комментарии в коде легко выявляются на этапе ревью кода, а вот просить разработчиков ставить себе swag cli и перегенеривать docs/ каждый раз кажется затратным, да и не барское это дело — автогенерируемый код ревьюить, поэтому пойдём иным путём. Напишем-ка Dockerfile, который бы и документацию генерировал, и приложение билдил, да ещё и с Multi-stage, чтобы по всем канонам. Получим что-то примерно такое:
FROM golang:1.21-alpine AS swag WORKDIR /app RUN apk add --no-cache git RUN go install github.com/swaggo/swag/cmd/swag@latest COPY . . RUN go mod tidy && swag init FROM golang:1.20-alpine AS builder WORKDIR /app RUN apk add --no-cache git COPY go.mod go.sum ./ RUN go mod tidy COPY --from=swag . . RUN go build -o /app/main . FROM scratch COPY --from=builder /app/main . RUN apk add --no-cache ca-certificates CMD ["./main"]
Безусловно это очень плохой Dockerfile, но цель была сделать его как можно понятнее и проще, а не применить все Best Practices.
С Dockerfile покончено. Теперь нужно сбилдить image и запушить в какое-нибудь хранилище. Для следующих шагов очень важно использовать инкрементируемый тег. Например используйте семантическое версионирование.
Деплой приложения
Микросервисы отлично смотрятся в кластере Kubernetes, поэтому задеплоим нашу поделку. Так как для работы всей схемы потребуется ArgoCD и ArgoCD Image Updater, сразу будем писать Helm-чарт. Упрощённо это будет выглядеть вот так:
// values.yaml replicaCount: 1 image: repository: registry.io/app tag: 0.0.1 pullPolicy: Always service: type: ClusterIP port: 8080
// templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }} labels: app: {{ .Chart.Name }} annotations: {{- with .Values.annotations }} {{ toYaml . | indent 4 }} {{- end }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ .Chart.Name }} template: metadata: labels: app: {{ .Chart.Name }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - containerPort: 8080
// templates/service.yaml apiVersion: v1 kind: Service metadata: name: {{ .Release.Name }} labels: app: {{ .Chart.Name }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: 8080 selector: app: {{ .Chart.Name }}
Помимо этого описываем ArgoCD Application:
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: app namespace: argocd annotations: argocd-image-updater.argoproj.io/image-list: app="registry.io/app" argocd-image-updater.argoproj.io/app.update-strategy: "semver" spec: project: default source: repoURL: <git-репозиторий с чартом> targetRevision: master path: charts/app helm: valueFiles: - values.yaml destination: server: https://kubernetes.default.svc namespace: swag-example syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true
Post-commit trigger
Этот раздел будет сугубо теоретическим, так как мы используем разные платформы для написания CI и глупо привязываться к чему-то конкретному.
Тем не менее, логика довольно тривиальна — мы уже написали Dockerfile, который умеет билдить приложение и генерировать документацию, уже применили GitOps и даже объяснили ArgoCD, что ему нужно следить за semver-тегами. Теперь дело за малым — научиться обновлять версии образов в автоматическом режиме. Для этого я предлагаю воспользоваться вебхуками, которые умеет отправлять и GitHub, и GitLab, и BitBucket, и даже отечественный GitFlic.
После того, как код прошёл ревью, в комментарии были внесены необходимые правки, а разработчик запушил свои изменения в master-ветку, платформа для CI получает вебхук и запускается пайплайн, который должен сбилдить образ и запушить новую версию в registry. Реализаций этой логики может быть много. Более того, эту логику можно расширить и разделить dev- и prod-контуры. Как бы там ни было, построение системы с постоянно обновляемой документацией завершено.
Заключение
Применение такого подхода позволит качественно документировать свой бэкенд и делать это автоматически, что уменьшает человеческий фактор и не позволит допустить устаревания документации. Да, безусловно предложенная концепция требует сформированной инфраструктуры в виде какой-либо платформы для CI и ArgoCD/FluxCD, но кажется, что такой стек встречается повсеместно. В крайнем случае, никто не мешает использовать тот же GitLab CI как инструмент деплоя — на мой личный взгляд это не так красиво, но зато просто.
Надеюсь статья покажется кому-то полезной и побудит попробовать сделать что-то подобное!
ссылка на оригинал статьи https://habr.com/ru/articles/863974/
Добавить комментарий