npm ci && npm test → go test ./... (быстрее!)
npm run lint → golangci-lint run
npm run build → go build (один бинарник)
Docker-сборка без node_modules → образ 8MB вместо 150MB
Кросс-компиляция из коробки: GOOS=linux GOARCH=amd64 go build
mkdir go-cicd && cd go-cicd
go mod init go-cicd
# Создаём структуру
mkdir -p cmd/server
mkdir -p internal/handler
mkdir -p .github/workflows
cmd/server/main.gopackage main
import (
"fmt"
"net/http"
"os"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
fmt.Printf("Server starting on :%s\n", port)
http.ListenAndServe(":"+port, mux)
}
internal/handler/handler.gopackage handler
func Greet(name string) string {
if name == "" {
name = "World"
}
return "Hello, " + name + "!"
}
func Add(a, b int) int {
return a + b
}
internal/handler/handler_test.gopackage handler_test
import (
"testing"
"go-cicd/internal/handler"
)
func TestGreet(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"with name", "Gopher", "Hello, Gopher!"},
{"empty name", "", "Hello, World!"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := handler.Greet(tt.input)
if result != tt.expected {
t.Errorf("Greet(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestAdd(t *testing.T) {
result := handler.Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) = %d, want 5", result)
}
}
.golangci.yml (конфигурация линтера)# Конфигурация golangci-lint (аналог .eslintrc)
run:
timeout: 5m
tests: true
go: '1.22'
linters:
enable:
- errcheck # Проверка необработанных ошибок
- gosimple # Упрощение кода
- govet # Стандартный анализатор Go
- ineffassign # Неиспользуемые присваивания
- staticcheck # Статический анализ
- unused # Неиспользуемый код
- gofmt # Форматирование
- goimports # Импорты
- misspell # Опечатки
- revive # Замена golint
- unconvert # Лишние преобразования типов
- unparam # Неиспользуемые параметры
- gocyclo # Цикломатическая сложность
- gocognit # Когнитивная сложность
- bodyclose # Проверка закрытия HTTP-ответов
linters-settings:
gocyclo:
min-complexity: 15
gocognit:
min-complexity: 30
revive:
rules:
- name: exported
arguments:
- disableStutteringCheck
misspell:
locale: US
issues:
exclude-rules:
- path: _test\.go
linters:
- gocyclo
- gocognit
max-issues-per-linter: 0
max-same-issues: 0
.github/workflows/ci.yml (основной CI)name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
GO_VERSION: '1.22'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ╔══════════════════════════════════════════════════════════╗
# ║ JOB 1: ЛИНТЕР + ТЕСТЫ ║
# ╚══════════════════════════════════════════════════════════╝
lint-and-test:
name: Lint & Test
runs-on: ubuntu-latest
# Matrix: запускаем на нескольких версиях Go
strategy:
matrix:
go-version: ['1.22', '1.21']
fail-fast: false # Не останавливать другие при ошибке
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true # Автоматическое кэширование go modules
- name: Verify dependencies
run: go mod verify
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: latest
args: --timeout=5m
- name: Run tests with race detector
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./coverage.out
flags: unittests
continue-on-error: true # Не фейлить билд если Codecov недоступен
- name: Run vet
run: go vet ./...
# ╔══════════════════════════════════════════════════════════╗
# ║ JOB 2: СБОРКА DOCKER-ОБРАЗА ║
# ╚══════════════════════════════════════════════════════════╝
build-and-push:
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: lint-and-test # Только после успешных тестов
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=sha,prefix=,format=short
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.sha }}
# ╔══════════════════════════════════════════════════════════╗
# ║ JOB 3: ДЕПЛОЙ В KUBERNETES ║
# ╚══════════════════════════════════════════════════════════╝
deploy:
name: Deploy to Kubernetes
runs-on: ubuntu-latest
needs: build-and-push
if: github.ref == 'refs/heads/main'
environment: production # Требует approval (если настроен)
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup kubectl
uses: azure/setup-kubectl@v3
- name: Set Kubernetes context
uses: azure/k8s-set-context@v3
with:
kubeconfig: ${{ secrets.KUBE_CONFIG }}
- name: Update image in deployment
run: |
kubectl set image deployment/go-app \
go-app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }} \
--namespace=production
- name: Wait for rollout
run: |
kubectl rollout status deployment/go-app --namespace=production --timeout=5m
- name: Verify deployment
run: |
kubectl get pods -l app=go-app --namespace=production
kubectl describe deployment go-app --namespace=production
.github/workflows/release.yml (релиз)name: Release
on:
push:
tags:
- 'v*' # v1.0.0, v2.1.3, etc.
jobs:
release:
name: Create Release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Run tests
run: go test -v -race ./...
- name: Build binaries (cross-compile)
run: |
GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o dist/app-linux-amd64 ./cmd/server
GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o dist/app-linux-arm64 ./cmd/server
GOOS=darwin GOARCH=amd64 go build -ldflags="-w -s" -o dist/app-darwin-amd64 ./cmd/server
GOOS=darwin GOARCH=arm64 go build -ldflags="-w -s" -o dist/app-darwin-arm64 ./cmd/server
GOOS=windows GOARCH=amd64 go build -ldflags="-w -s" -o dist/app-windows-amd64.exe ./cmd/server
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v1
with:
name: Release ${{ github.ref_name }}
body_path: CHANGELOG.md
files: dist/*
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.github/workflows/dependency-review.yml (безопасность)name: Dependency Review
on:
pull_request:
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Dependency Review
uses: actions/dependency-review-action@v4
Makefile# Локальные команды (аналогичны CI)
.PHONY: lint test build clean
lint: ## Запустить линтер
golangci-lint run ./...
test: ## Запустить тесты
go test -v -race -cover ./...
test-coverage: ## Тесты с покрытием
go test -v -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
build: ## Собрать бинарник
CGO_ENABLED=0 go build -ldflags="-w -s" -o bin/server ./cmd/server
clean: ## Очистить
rm -rf bin/ dist/ coverage.out coverage.html
DockerfileFROM golang:1.22-alpine AS builder
RUN apk add --no-cache git ca-certificates
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /server ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /server /server
EXPOSE 8080
USER nonroot
ENTRYPOINT ["/server"]
# Локальный запуск линтера
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run ./...
# Локальный запуск тестов
go test -v -race ./...
# Сборка Docker-образа
docker build -t go-cicd-app .
# Структура репозитория для GitHub:
# .github/workflows/ci.yml (основной CI)
# .github/workflows/release.yml (релизы)
# .golangci.yml (конфигурация линтера)
# Dockerfile
# Makefile
# Secrets для GitHub Actions:
# KUBE_CONFIG — kubeconfig для деплоя
# (опционально) CODECOV_TOKEN
| Этап | Job | Триггер | Инструменты |
|---|---|---|---|
| 1. Линт | lint-and-test | push, PR | golangci-lint |
| 2. Тесты | lint-and-test | push, PR | go test -race |
| 3. Сборка образа | build-and-push | main branch | Docker Buildx |
| 4. Push в registry | build-and-push | main branch | ghcr.io |
| 5. Деплой в K8s | deploy | main branch | kubectl |
| 6. Релиз | release | tag v* | GoReleaser / GitHub |
💡 Best practices от сеньоров:
💡 Для Node.js разработчика:
npm ci && npm test → go test ./... (быстрее, нет node_modules).npm run lint (ESLint) → golangci-lint run (быстрее, единый бинарник).