Лаб. работа "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
Основная структура
1stages: # Определение стадий
2 - build
3 - test
4 - deploy
5
6variables: # Глобальные переменные
7 DOCKER_DRIVER: overlay2
8 DOCKER_TLS_CERTDIR: "/certs"
9
10job_name: # Имя job'а
11 stage: build # Стадия выполнения
12 image: node:18 # Docker образ
13 script: # Команды для выполнения
14 - npm install
15 - npm run build
16 artifacts: # Артефакты
17 paths:
18 - dist/
19 expire_in: 1 week
20 only: # Условия запуска
21 - main
22 - 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:
1ssh-keygen -t ed25519 -C "your.email@example.com"Добавьте публичный ключ в GitLab: Profile → SSH Keys → Add new key
Настройка Git локально Настройте Git с вашими данными:
1git config --global user.name "Ваше Имя" 2git config --global user.email "your.email@example.com"
Часть 2. Создание проекта
Создание нового проекта В GitLab создайте новый проект:
- Нажмите “New project”
- Выберите “Create blank project”
- Введите название:
cicd-lab-work - Описание:
Лабораторная работа по CI/CD - Установите видимость: Private
- Нажмите “Create project”
Клонирование репозитория Склонируйте созданный репозиторий:
1git clone http://it.vstu.by/gitlab/ваш-username/cicd-lab-work.git 2cd cicd-lab-work
Часть 3. Создание простого приложения
Создание Node.js приложения Создайте простое веб-приложение:
1# package.json 2{ 3 "name": "cicd-lab-app", 4 "version": "1.0.0", 5 "description": "Simple app for CI/CD lab", 6 "main": "app.js", 7 "scripts": { 8 "start": "node app.js", 9 "test": "jest", 10 "test:watch": "jest --watch", 11 "lint": "eslint .", 12 "build": "npm run lint && npm test" 13 }, 14 "dependencies": { 15 "express": "^4.18.2" 16 }, 17 "devDependencies": { 18 "jest": "^29.7.0", 19 "supertest": "^6.3.3", 20 "eslint": "^8.50.0" 21 }, 22 "jest": { 23 "testEnvironment": "node" 24 } 25}Создание основного файла приложения Создайте
app.js:1const express = require('express'); 2const app = express(); 3const PORT = process.env.PORT || 3000; 4 5app.use(express.json()); 6 7// Health check endpoint 8app.get('/health', (req, res) => { 9 res.json({ 10 status: 'healthy', 11 timestamp: new Date().toISOString(), 12 version: '1.0.0' 13 }); 14}); 15 16// Main endpoint 17app.get('/', (req, res) => { 18 res.json({ 19 message: 'Hello from CI/CD Lab App!', 20 environment: process.env.NODE_ENV || 'development', 21 timestamp: new Date().toISOString() 22 }); 23}); 24 25// API endpoint with basic functionality 26app.get('/api/data', (req, res) => { 27 res.json({ 28 data: [ 29 { id: 1, name: 'Item 1', status: 'active' }, 30 { id: 2, name: 'Item 2', status: 'inactive' }, 31 { id: 3, name: 'Item 3', status: 'active' } 32 ], 33 total: 3, 34 timestamp: new Date().toISOString() 35 }); 36}); 37 38// Error handling middleware 39app.use((err, req, res, next) => { 40 console.error(err.stack); 41 res.status(500).json({ 42 error: 'Something went wrong!', 43 timestamp: new Date().toISOString() 44 }); 45}); 46 47app.listen(PORT, () => { 48 console.log(`Server is running on port ${PORT}`); 49}); 50 51module.exports = app;Создание тестов Создайте папку
testsи файлapp.test.js:1const request = require('supertest'); 2const app = require('../app'); 3 4describe('API Endpoints', () => { 5 test('GET / should return welcome message', async () => { 6 const response = await request(app) 7 .get('/') 8 .expect('Content-Type', /json/) 9 .expect(200); 10 11 expect(response.body).toHaveProperty('message'); 12 expect(response.body.message).toContain('Hello from CI/CD Lab App!'); 13 }); 14 15 test('GET /health should return health status', async () => { 16 const response = await request(app) 17 .get('/health') 18 .expect('Content-Type', /json/) 19 .expect(200); 20 21 expect(response.body).toHaveProperty('status', 'healthy'); 22 expect(response.body).toHaveProperty('timestamp'); 23 }); 24 25 test('GET /api/data should return data array', async () => { 26 const response = await request(app) 27 .get('/api/data') 28 .expect('Content-Type', /json/) 29 .expect(200); 30 31 expect(response.body).toHaveProperty('data'); 32 expect(response.body).toHaveProperty('total', 3); 33 expect(Array.isArray(response.body.data)).toBe(true); 34 }); 35 36 test('GET /nonexistent should return 404', async () => { 37 await request(app) 38 .get('/nonexistent') 39 .expect(404); 40 }); 41}); 42 43describe('Application Logic', () => { 44 test('should handle missing environment variables', () => { 45 delete process.env.PORT; 46 const testApp = require('../app'); 47 expect(testApp).toBeDefined(); 48 }); 49});Создание Dockerfile Создайте
Dockerfile:1# Build stage 2FROM node:18-alpine AS builder 3 4WORKDIR /app 5 6# Copy package files 7COPY package*.json ./ 8 9# Install dependencies 10RUN npm ci --only=production 11 12# Production stage 13FROM node:18-alpine 14 15WORKDIR /app 16 17# Copy built application 18COPY --from=builder /app/node_modules ./node_modules 19COPY . . 20 21# Create non-root user 22RUN addgroup -g 1001 -S nodejs 23RUN adduser -S nodejs -u 1001 24 25# Change ownership 26RUN chown -R nodejs:nodejs /app 27USER nodejs 28 29# Expose port 30EXPOSE 3000 31 32# Health check 33HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ 34 CMD node healthcheck.js 35 36# Start application 37CMD ["node", "app.js"]Создание healthcheck.js Создайте
healthcheck.js:1const http = require('http'); 2 3const options = { 4 hostname: 'localhost', 5 port: process.env.PORT || 3000, 6 path: '/health', 7 method: 'GET', 8 timeout: 2000 9}; 10 11const req = http.request(options, (res) => { 12 if (res.statusCode === 200) { 13 process.exit(0); 14 } else { 15 process.exit(1); 16 } 17}); 18 19req.on('error', () => { 20 process.exit(1); 21}); 22 23req.on('timeout', () => { 24 req.destroy(); 25 process.exit(1); 26}); 27 28req.end();
Часть 4. Создание базового .gitlab-ci.yml
Создание простого пайплайна Создайте файл
.gitlab-ci.ymlв корне проекта:1stages: 2 - install 3 - test 4 - build 5 - deploy 6 7variables: 8 NODE_VERSION: "18" 9 DOCKER_DRIVER: overlay2 10 11# Install dependencies 12install_dependencies: 13 stage: install 14 image: node:${NODE_VERSION} 15 script: 16 - npm ci 17 cache: 18 paths: 19 - node_modules/ 20 artifacts: 21 paths: 22 - node_modules/ 23 expire_in: 1 hour 24 25# Run tests 26test: 27 stage: test 28 image: node:${NODE_VERSION} 29 dependencies: 30 - install_dependencies 31 script: 32 - npm test 33 coverage: '/Coverage: \d+\.\d+%/' 34 artifacts: 35 reports: 36 junit: junit.xml 37 paths: 38 - coverage/ 39 expire_in: 1 week 40 41# Build Docker image 42build_docker: 43 stage: build 44 image: docker:latest 45 services: 46 - docker:dind 47 dependencies: 48 - install_dependencies 49 script: 50 - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . 51 - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA 52 only: 53 - main 54 - develop 55 - merge_requests 56 57# Deploy to staging (manual) 58deploy_staging: 59 stage: deploy 60 image: alpine:latest 61 script: 62 - echo "Deploying to staging environment..." 63 - echo "Application version: $CI_COMMIT_SHA" 64 - echo "Deployed at: $(date)" 65 environment: 66 name: staging 67 url: https://staging.example.com 68 when: manual 69 only: 70 - mainПервый коммит и push Добавьте файлы в Git и отправьте в GitLab:
1git add . 2git commit -m "Initial commit: Add basic application and CI/CD pipeline" 3git push origin mainПроверка пайплайна Перейдите в GitLab → CI/CD → Pipelines и посмотрите на выполнение пайплайна.
Часть 5. Расширение пайплайна
Добавление линтинга Обновите
.gitlab-ci.yml, добавив стадию линтинга:1stages: 2 - install 3 - lint 4 - test 5 - build 6 - deploy 7 8# Lint code 9lint: 10 stage: lint 11 image: node:${NODE_VERSION} 12 dependencies: 13 - install_dependencies 14 script: 15 - npm run lint 16 allow_failure: falseДобавление security сканирования Добавьте job для проверки безопасности:
1# Security scan 2security_scan: 3 stage: test 4 image: node:${NODE_VERSION} 5 dependencies: 6 - install_dependencies 7 script: 8 - npm audit --audit-level moderate 9 allow_failure: true 10 only: 11 - main 12 - merge_requestsДобавление интеграционных тестов Создайте интеграционные тесты в файле
tests/integration.test.js:1const request = require('supertest'); 2const app = require('../app'); 3 4describe('Integration Tests', () => { 5 let server; 6 7 beforeAll((done) => { 8 server = app.listen(0, () => { 9 done(); 10 }); 11 }); 12 13 afterAll((done) => { 14 server.close(done); 15 }); 16 17 test('should handle concurrent requests', async () => { 18 const promises = []; 19 for (let i = 0; i < 10; i++) { 20 promises.push(request(app).get('/api/data')); 21 } 22 23 const responses = await Promise.all(promises); 24 25 responses.forEach(response => { 26 expect(response.status).toBe(200); 27 expect(response.body).toHaveProperty('data'); 28 }); 29 }); 30 31 test('should maintain data consistency', async () => { 32 const response1 = await request(app).get('/api/data'); 33 const response2 = await request(app).get('/api/data'); 34 35 expect(response1.body.total).toBe(response2.body.total); 36 expect(response1.body.data.length).toBe(response2.body.data.length); 37 }); 38});Обновление package.json Добавьте скрипт для интеграционных тестов:
1{ 2 "scripts": { 3 "test": "jest", 4 "test:integration": "jest --testPathPattern=integration", 5 "test:unit": "jest --testPathPattern=unit" 6 } 7}Обновление .gitlab-ci.yml Добавьте job для интеграционных тестов:
1# Integration tests 2integration_tests: 3 stage: test 4 image: node:${NODE_VERSION} 5 dependencies: 6 - install_dependencies 7 script: 8 - npm run test:integration 9 artifacts: 10 reports: 11 junit: junit-integration.xml 12 paths: 13 - coverage-integration/ 14 expire_in: 1 week 15 only: 16 - main 17 - merge_requests
Часть 6. Работа с артефактами и кэшем
Улучшение кэширования Обновите настройки кэша в
.gitlab-ci.yml:1cache: 2 paths: 3 - node_modules/ 4 - .npm/ 5 key: 6 files: 7 - package-lock.json 8 policy: pull-pushНастройка артефактов Добавьте более детальные настройки артефактов:
1test: 2 stage: test 3 image: node:${NODE_VERSION} 4 dependencies: 5 - install_dependencies 6 script: 7 - npm test 8 coverage: '/Coverage: \d+\.\d+%/' 9 artifacts: 10 when: always 11 reports: 12 junit: junit.xml 13 coverage_report: 14 coverage_format: cobertura 15 path: coverage/cobertura-coverage.xml 16 paths: 17 - coverage/ 18 - junit.xml 19 expire_in: 30 days 20 coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
Часть 7. Настройка окружений
Добавление окружений Обновите
.gitlab-ci.ymlс настройками окружений:1# Deploy to staging 2deploy_staging: 3 stage: deploy 4 image: alpine:latest 5 script: 6 - echo "Deploying to staging..." 7 - echo "Environment: staging" 8 - echo "Version: $CI_COMMIT_SHA" 9 - echo "Branch: $CI_COMMIT_REF_NAME" 10 - echo "Author: $GITLAB_USER_NAME" 11 environment: 12 name: staging 13 url: https://staging.example.com 14 on_stop: stop_staging 15 when: manual 16 only: 17 - main 18 19# Stop staging environment 20stop_staging: 21 stage: deploy 22 image: alpine:latest 23 script: 24 - echo "Stopping staging environment..." 25 environment: 26 name: staging 27 action: stop 28 when: manual 29 only: 30 - main 31 32# Deploy to production 33deploy_production: 34 stage: deploy 35 image: alpine:latest 36 script: 37 - echo "Deploying to production..." 38 - echo "Environment: production" 39 - echo "Version: $CI_COMMIT_SHA" 40 environment: 41 name: production 42 url: https://production.example.com 43 when: manual 44 only: 45 - tags 46 dependencies: 47 - build_docker
Часть 8. Создание merge request
Создание feature ветки Создайте новую ветку для добавления функции:
1git checkout -b feature/add-user-endpointДобавление нового endpoint Обновите
app.js, добавив новый endpoint:1// Add user endpoint 2app.post('/api/users', (req, res) => { 3 const { name, email } = req.body; 4 5 if (!name || !email) { 6 return res.status(400).json({ 7 error: 'Name and email are required' 8 }); 9 } 10 11 const newUser = { 12 id: Date.now(), 13 name, 14 email, 15 createdAt: new Date().toISOString() 16 }; 17 18 res.status(201).json({ 19 message: 'User created successfully', 20 user: newUser 21 }); 22});Добавление тестов Создайте тесты для нового endpoint в
tests/users.test.js:1const request = require('supertest'); 2const app = require('../app'); 3 4describe('User Endpoints', () => { 5 test('POST /api/users should create a new user', async () => { 6 const userData = { 7 name: 'John Doe', 8 email: 'john@example.com' 9 }; 10 11 const response = await request(app) 12 .post('/api/users') 13 .send(userData) 14 .expect('Content-Type', /json/) 15 .expect(201); 16 17 expect(response.body).toHaveProperty('message', 'User created successfully'); 18 expect(response.body).toHaveProperty('user'); 19 expect(response.body.user).toHaveProperty('name', userData.name); 20 expect(response.body.user).toHaveProperty('email', userData.email); 21 }); 22 23 test('POST /api/users should return 400 for missing data', async () => { 24 const response = await request(app) 25 .post('/api/users') 26 .send({ name: 'John Doe' }) 27 .expect(400); 28 29 expect(response.body).toHaveProperty('error'); 30 }); 31});Коммит и push
1git add . 2git commit -m "feat: Add user creation endpoint" 3git 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:1# Nightly security scan 2security_scan_nightly: 3 stage: test 4 image: node:${NODE_VERSION} 5 script: 6 - npm audit --audit-level high 7 - echo "Running security scan..." 8 only: 9 - schedules 10 allow_failure: trueСоздание расписания В 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 информацию:
1debug_info: 2 stage: test 3 image: alpine:latest 4 script: 5 - echo "=== Debug Information ===" 6 - echo "Commit SHA: $CI_COMMIT_SHA" 7 - echo "Branch: $CI_COMMIT_REF_NAME" 8 - echo "User: $GITLAB_USER_NAME" 9 - echo "Pipeline ID: $CI_PIPELINE_ID" 10 - echo "Job ID: $CI_JOB_ID" 11 - echo "Environment: $CI_ENVIRONMENT_NAME" 12 - echo "========================" 13 when: manual 14 only: 15 - 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 в реальных проектах