Лаб. работа “CI/CD с GitLab”
Лабораторная работа по теме “CI/CD с GitLab”
Цель работы
Изучить практические аспекты настройки и использования системы непрерывной интеграции и развертывания (CI/CD) с использованием GitLab. Получить навыки создания пайплайнов, настройки автоматического тестирования, сборки и развертывания приложений.
Краткие теоретические сведения
GitLab CI/CD — это встроенная в GitLab система непрерывной интеграции и развертывания, которая позволяет автоматизировать процессы разработки, тестирования и развертывания приложений.
Ключевые концепции GitLab CI/CD:
- Runner — агент, который выполняет задачи пайплайна. Может быть общим, групповым или специфичным для проекта.
- Pipeline — набор задач (jobs), объединенных в стадии (stages), которые выполняются в определенном порядке.
- Job — минимальная единица выполнения в пайплайне, содержит скрипты для выполнения.
- Stage — группа jobs, которые выполняются параллельно. Стадии выполняются последовательно.
- Artifacts — файлы, создаваемые job’ом и передаваемые между стадиями.
- Variables — переменные окружения для конфигурации пайплайна.
- Cache — механизм кэширования зависимостей для ускорения сборок.
Преимущества GitLab CI/CD: * Полная интеграция с GitLab * Встроенная поддержка Docker * Автоматическое обнаружение .gitlab-ci.yml * Богатые возможности визуализации * Поддержка множественных раннеров * Встроенный registry для Docker образов
Краткий справочник по .gitlab-ci.yml
Основная структура
stages: # Определение стадий
- build
- test
- deploy
variables: # Глобальные переменные
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
job_name: # Имя job'а
stage: build # Стадия выполнения
image: node:18 # Docker образ
script: # Команды для выполнения
- npm install
- npm run build
artifacts: # Артефакты
paths:
- dist/
expire_in: 1 week
only: # Условия запуска
- main
- developОсновные ключевые слова
stages— список стадий пайплайнаstage— стадия для конкретного job’аimage— Docker образ для выполнения job’аservices— дополнительные Docker сервисыscript— команды для выполненияbefore_script— команды перед основным скриптомafter_script— команды после основного скриптаartifacts— файлы для сохраненияcache— кэширование файловonly/except— условия запуска job’аvariables— переменные окруженияdependencies— зависимости от других job’овallow_failure— разрешение на неудачуwhen— условие выполнения (on_success, on_failure, always)
Типы пайплайнов
- Branch Pipelines — запускаются при push в ветки
- Merge Request Pipelines — запускаются при создании MR
- Scheduled Pipelines — запускаются по расписанию
- Manual Pipelines — запускаются вручную
- Triggered Pipelines — запускаются через API
Требования к окружению
- Доступ к GitLab серверу:
it.vstu.by/gitlab - Учетная запись в GitLab
- Git установлен локально
- Docker установлен локально (для локального тестирования)
- Базовые знания Git
- Текстовый редактор
Порядок выполнения работы
Часть 1. Подключение к GitLab
Регистрация/вход в GitLab Перейдите по адресу
http://it.vstu.by/gitlabи войдите в систему под своей учетной записью.Настройка SSH ключей (рекомендуется) Сгенерируйте SSH ключ для безопасной работы с Git:
Добавьте публичный ключ в GitLab: Profile → SSH Keys → Add new key
Настройка Git локально Настройте Git с вашими данными:
Часть 2. Создание проекта
Создание нового проекта В GitLab создайте новый проект:
- Нажмите “New project”
- Выберите “Create blank project”
- Введите название:
cicd-lab-work - Описание:
Лабораторная работа по CI/CD - Установите видимость: Private
- Нажмите “Create project”
Клонирование репозитория Склонируйте созданный репозиторий:
Часть 3. Создание простого приложения
Создание Node.js приложения Создайте простое веб-приложение:
# package.json { "name": "cicd-lab-app", "version": "1.0.0", "description": "Simple app for CI/CD lab", "main": "app.js", "scripts": { "start": "node app.js", "test": "jest", "test:watch": "jest --watch", "lint": "eslint .", "build": "npm run lint && npm test" }, "dependencies": { "express": "^4.18.2" }, "devDependencies": { "jest": "^29.7.0", "supertest": "^6.3.3", "eslint": "^8.50.0" }, "jest": { "testEnvironment": "node" } }Создание основного файла приложения Создайте
app.js:const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; app.use(express.json()); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), version: '1.0.0' }); }); // Main endpoint app.get('/', (req, res) => { res.json({ message: 'Hello from CI/CD Lab App!', environment: process.env.NODE_ENV || 'development', timestamp: new Date().toISOString() }); }); // API endpoint with basic functionality app.get('/api/data', (req, res) => { res.json({ data: [ { id: 1, name: 'Item 1', status: 'active' }, { id: 2, name: 'Item 2', status: 'inactive' }, { id: 3, name: 'Item 3', status: 'active' } ], total: 3, timestamp: new Date().toISOString() }); }); // Error handling middleware app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: 'Something went wrong!', timestamp: new Date().toISOString() }); }); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); module.exports = app;Создание тестов Создайте папку
testsи файлapp.test.js:const request = require('supertest'); const app = require('../app'); describe('API Endpoints', () => { test('GET / should return welcome message', async () => { const response = await request(app) .get('/') .expect('Content-Type', /json/) .expect(200); expect(response.body).toHaveProperty('message'); expect(response.body.message).toContain('Hello from CI/CD Lab App!'); }); test('GET /health should return health status', async () => { const response = await request(app) .get('/health') .expect('Content-Type', /json/) .expect(200); expect(response.body).toHaveProperty('status', 'healthy'); expect(response.body).toHaveProperty('timestamp'); }); test('GET /api/data should return data array', async () => { const response = await request(app) .get('/api/data') .expect('Content-Type', /json/) .expect(200); expect(response.body).toHaveProperty('data'); expect(response.body).toHaveProperty('total', 3); expect(Array.isArray(response.body.data)).toBe(true); }); test('GET /nonexistent should return 404', async () => { await request(app) .get('/nonexistent') .expect(404); }); }); describe('Application Logic', () => { test('should handle missing environment variables', () => { delete process.env.PORT; const testApp = require('../app'); expect(testApp).toBeDefined(); }); });Создание Dockerfile Создайте
Dockerfile:# Build stage FROM node:18-alpine AS builder WORKDIR /app # Copy package files COPY package*.json ./ # Install dependencies RUN npm ci --only=production # Production stage FROM node:18-alpine WORKDIR /app # Copy built application COPY --from=builder /app/node_modules ./node_modules COPY . . # Create non-root user RUN addgroup -g 1001 -S nodejs RUN adduser -S nodejs -u 1001 # Change ownership RUN chown -R nodejs:nodejs /app USER nodejs # Expose port EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node healthcheck.js # Start application CMD ["node", "app.js"]Создание healthcheck.js Создайте
healthcheck.js:const http = require('http'); const options = { hostname: 'localhost', port: process.env.PORT || 3000, path: '/health', method: 'GET', timeout: 2000 }; const req = http.request(options, (res) => { if (res.statusCode === 200) { process.exit(0); } else { process.exit(1); } }); req.on('error', () => { process.exit(1); }); req.on('timeout', () => { req.destroy(); process.exit(1); }); req.end();
Часть 4. Создание базового .gitlab-ci.yml
Создание простого пайплайна Создайте файл
.gitlab-ci.ymlв корне проекта:stages: - install - test - build - deploy variables: NODE_VERSION: "18" DOCKER_DRIVER: overlay2 # Install dependencies install_dependencies: stage: install image: node:${NODE_VERSION} script: - npm ci cache: paths: - node_modules/ artifacts: paths: - node_modules/ expire_in: 1 hour # Run tests test: stage: test image: node:${NODE_VERSION} dependencies: - install_dependencies script: - npm test coverage: '/Coverage: \d+\.\d+%/' artifacts: reports: junit: junit.xml paths: - coverage/ expire_in: 1 week # Build Docker image build_docker: stage: build image: docker:latest services: - docker:dind dependencies: - install_dependencies script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA only: - main - develop - merge_requests # Deploy to staging (manual) deploy_staging: stage: deploy image: alpine:latest script: - echo "Deploying to staging environment..." - echo "Application version: $CI_COMMIT_SHA" - echo "Deployed at: $(date)" environment: name: staging url: https://staging.example.com when: manual only: - mainПервый коммит и push Добавьте файлы в Git и отправьте в GitLab:
Проверка пайплайна Перейдите в GitLab → CI/CD → Pipelines и посмотрите на выполнение пайплайна.
Часть 5. Расширение пайплайна
Добавление линтинга Обновите
.gitlab-ci.yml, добавив стадию линтинга:Добавление security сканирования Добавьте job для проверки безопасности:
Добавление интеграционных тестов Создайте интеграционные тесты в файле
tests/integration.test.js:const request = require('supertest'); const app = require('../app'); describe('Integration Tests', () => { let server; beforeAll((done) => { server = app.listen(0, () => { done(); }); }); afterAll((done) => { server.close(done); }); test('should handle concurrent requests', async () => { const promises = []; for (let i = 0; i < 10; i++) { promises.push(request(app).get('/api/data')); } const responses = await Promise.all(promises); responses.forEach(response => { expect(response.status).toBe(200); expect(response.body).toHaveProperty('data'); }); }); test('should maintain data consistency', async () => { const response1 = await request(app).get('/api/data'); const response2 = await request(app).get('/api/data'); expect(response1.body.total).toBe(response2.body.total); expect(response1.body.data.length).toBe(response2.body.data.length); }); });Обновление package.json Добавьте скрипт для интеграционных тестов:
Обновление .gitlab-ci.yml Добавьте job для интеграционных тестов:
Часть 6. Работа с артефактами и кэшем
Улучшение кэширования Обновите настройки кэша в
.gitlab-ci.yml:Настройка артефактов Добавьте более детальные настройки артефактов:
test: stage: test image: node:${NODE_VERSION} dependencies: - install_dependencies script: - npm test coverage: '/Coverage: \d+\.\d+%/' artifacts: when: always reports: junit: junit.xml coverage_report: coverage_format: cobertura path: coverage/cobertura-coverage.xml paths: - coverage/ - junit.xml expire_in: 30 days coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
Часть 7. Настройка окружений
Добавление окружений Обновите
.gitlab-ci.ymlс настройками окружений:# Deploy to staging deploy_staging: stage: deploy image: alpine:latest script: - echo "Deploying to staging..." - echo "Environment: staging" - echo "Version: $CI_COMMIT_SHA" - echo "Branch: $CI_COMMIT_REF_NAME" - echo "Author: $GITLAB_USER_NAME" environment: name: staging url: https://staging.example.com on_stop: stop_staging when: manual only: - main # Stop staging environment stop_staging: stage: deploy image: alpine:latest script: - echo "Stopping staging environment..." environment: name: staging action: stop when: manual only: - main # Deploy to production deploy_production: stage: deploy image: alpine:latest script: - echo "Deploying to production..." - echo "Environment: production" - echo "Version: $CI_COMMIT_SHA" environment: name: production url: https://production.example.com when: manual only: - tags dependencies: - build_docker
Часть 8. Создание merge request
Создание feature ветки Создайте новую ветку для добавления функции:
Добавление нового endpoint Обновите
app.js, добавив новый endpoint:// Add user endpoint app.post('/api/users', (req, res) => { const { name, email } = req.body; if (!name || !email) { return res.status(400).json({ error: 'Name and email are required' }); } const newUser = { id: Date.now(), name, email, createdAt: new Date().toISOString() }; res.status(201).json({ message: 'User created successfully', user: newUser }); });Добавление тестов Создайте тесты для нового endpoint в
tests/users.test.js:const request = require('supertest'); const app = require('../app'); describe('User Endpoints', () => { test('POST /api/users should create a new user', async () => { const userData = { name: 'John Doe', email: 'john@example.com' }; const response = await request(app) .post('/api/users') .send(userData) .expect('Content-Type', /json/) .expect(201); expect(response.body).toHaveProperty('message', 'User created successfully'); expect(response.body).toHaveProperty('user'); expect(response.body.user).toHaveProperty('name', userData.name); expect(response.body.user).toHaveProperty('email', userData.email); }); test('POST /api/users should return 400 for missing data', async () => { const response = await request(app) .post('/api/users') .send({ name: 'John Doe' }) .expect(400); expect(response.body).toHaveProperty('error'); }); });Коммит и push
bash git add . git commit -m "feat: Add user creation endpoint" git push origin feature/add-user-endpointСоздание Merge Request В GitLab создайте Merge Request:
- Нажмите “Create merge request”
- Выберите source branch:
feature/add-user-endpoint - Target branch:
main - Добавьте описание изменений
- Нажмите “Create merge request”
Проверка MR пайплайна Посмотрите, как запустился пайплайн для Merge Request.
Часть 9. Настройка scheduled пайплайнов
Добавление scheduled job Добавьте в
.gitlab-ci.yml:Создание расписания В GitLab: Settings → CI/CD → Schedules → New Schedule
- Description:
Nightly Security Scan - Cron:
0 2 * * *(каждый день в 2:00) - Target branch:
main - Нажмите “Add pipeline schedule”
- Description:
Часть 10. Мониторинг и отладка
Просмотр логов пайплайна В GitLab перейдите в CI/CD → Pipelines → выберите пайплайн → Jobs → конкретный job
Использование переменных Добавьте debug информацию:
debug_info: stage: test image: alpine:latest script: - echo "=== Debug Information ===" - echo "Commit SHA: $CI_COMMIT_SHA" - echo "Branch: $CI_COMMIT_REF_NAME" - echo "User: $GITLAB_USER_NAME" - echo "Pipeline ID: $CI_PIPELINE_ID" - echo "Job ID: $CI_JOB_ID" - echo "Environment: $CI_ENVIRONMENT_NAME" - echo "========================" when: manual only: - mainНастройка уведомлений В GitLab: Settings → Integrations → настройте Slack, email или другие уведомления
Задания для самостоятельного выполнения
Создание multi-stage Docker build Оптимизируйте Dockerfile для использования multi-stage build и уменьшения размера образа.
Добавление performance тестов Создайте performance тесты и добавьте их в пайплайн с отдельной стадией.
Настройка blue-green deployment Реализуйте стратегию blue-green deployment для production окружения.
Создание API документации Добавьте автоматическую генерацию API документации и включите ее в артефакты.
Настройка мониторинга Интегрируйте мониторинг приложения с Prometheus и добавьте соответствующие проверки в пайплайн.
Создание feature flags Реализуйте систему feature flags и добавьте соответствующие тесты.
Настройка rollback стратегии Создайте автоматический rollback при неудачном деплое.
Добавление code quality метрик Интегрируйте SonarQube или другие инструменты анализа качества кода.
Контрольные вопросы
- Что такое GitLab Runner и какие типы раннеров существуют?
- В чем разница между stages и jobs в GitLab CI/CD?
- Как настроить кэширование зависимостей в пайплайне?
- Что такое артефакты и как они используются между job’ами?
- Как настроить различные окружения (staging, production)?
- В чем преимущество Merge Request пайплайнов?
- Как настроить scheduled пайплайны?
- Что такое variables в GitLab CI/CD и как их использовать?
- Как настроить автоматический rollback при неудачном деплое?
- Какие best practices следует соблюдать при создании .gitlab-ci.yml?
Требования к отчету
Отчет по лабораторной работе должен содержать:
- Титульный лист
- Цель работы
- Пошаговое описание выполнения работы с приведением всех вводимых команд и их выводов
- Содержимое созданного .gitlab-ci.yml файла с подробными комментариями
- Скриншоты выполнения пайплайнов в GitLab
- Результаты тестирования всех стадий пайплайна
- Описание созданного Merge Request и его обработки
- Письменные ответы на контрольные вопросы
- Вывод, в котором необходимо кратко описать полученные знания и навыки, а также привести примеры использования GitLab CI/CD в реальных проектах