Русский
Русский
English
Статистика
Реклама

Pipeline

Перевод Dockle Диагностика безопасности контейнеров

07.06.2021 20:11:26 | Автор: admin

В этой статье мы рассмотрим Dockle инструмент для проверки безопасности образов контейнеров, который можно использовать для поиска уязвимостей. Кроме того, с его помощью можно выполнять проверку на соответствие Best Practice, чтобы убедиться, что образ действительно создаётся на основе сохраненной истории команд.

Установка Dockle

Трудностей при установке утилиты возникнуть не должно:

  • Установка в OSX

$ brew install goodwithtech/r/dockle
  • Установка в Linux

# RHEL$ VERSION=$( curl --silent "https://api.github.com/repos/goodwithtech/dockle/releases/latest" | \ grep '"tag_name":' | \ sed -E 's/.*"v([^"]+)".*/\1/' \) && rpm -ivh https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.rpm#Ubuntu$ VERSION=$( curl --silent "https://api.github.com/repos/goodwithtech/dockle/releases/latest" | \ grep '"tag_name":' | \ sed -E 's/.*"v([^"]+)".*/\1/' \) && curl -L -o dockle.deb https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.deb$ sudo dpkg -i dockle.deb && rm dockle.deb

После установки утилиты всё, что вам нужно сделать, это указать имя образа. Больше ничего настраивать не нужно, вам даже не потребуется Docker.

Пример использования Dockle

Запускаем утилиту, указав имя образа. Если проблем нет, в консоль будет выведено сообщение PASS, а при обнаружении проблем или уязвимостей подробная информация о них:

Попробуем запустить Dockle в Docker, на скриншоте видно, что утилита отлично работает:

Основные функции и преимущества Dockle

  • поиск уязвимостей в образах,

  • помощь в создании правильного Dockerfile,

  • простота в использовании, нужно указать только имя изображения,

  • поддержка CIS Benchmarks.

Сравнение с другими инструментами

Существует большое количество похожих инструментов для диагностики безопасности, например: Docker Bench или Hadolint. Но в сравнении с ними Dockle более функциональна:

Применение Dockle в DevSecOps

Думаю, что эта небольшая статья хорошая отправная точка для начала работы с Dockle. Она может легко использоваться в CI, улучшит ваши пайплайны в соответствии с концепцией DevSecOps.

По ссылкам ниже вы можете найти примеры того, как настроить системы CI / CD для работы с Dockle:

Подробнее..

Как писать кодогенераторы в Go

03.06.2021 16:16:28 | Автор: admin

Однообразный код писать неинтересно, нудно, но приходится. Испокон веков изворотливые программисты ищут Святой Грааль формализма, позволяющего переложить рутинные задачи на машину, писать только раз и переиспользовать код. Так появились структурное программирование, потом объектно-ориентированное, полиморфизм с параметризованными типами, кодогенерация на основе формальных грамматик, препроцессоры макроязыка и прочее Под катом рассмотрим, как обстоят дела именно в Go.


В Go на сегодня generics нет (хоть третий год и обещают), а выписывать по шаблону GetMax([]MyType) для каждого MyType надоедает.

Параметрический полиморфизм можно реализовать генерацией частных форм обобщённого кода на стадии компиляции (или выполнения) и поддержкой таблиц соответствия на стадии выполнения. В Go поддерживаются таблицы методов для типов и интерфейсов и диспетчеризация этих таблиц просто, зато эффективно реализовано.

Runtime-доступ к диспетчеру предоставлен пакетом reflect, что обеспечивает сильный, но дорогостоящий механизм интроспекции, позволяющий динамически оперировать статически заявленными типами. Вызовы reflect затратны, но, например, в C нет и этого, там на стадии выполнения данные не знают, какого они типа.

Стандартного препроцессора в Go тоже нет. Зато есть директива go:generate и есть доступ к потрохам компилятора, в частности к дереву разбора (Abstract Syntax Tree), в пакетах go/ стандартной библиотеки. Это в совокупности даёт инструментарий богаче, чем препроцессор макросов.

Идиоматическое применение интерфейсов реализовано в stdlib-пакете sort, интроспекция применяется в пакетах encoding и fmt, go:generate в придворном пакете golang.org/x/tools/cmd/stringer.

Манипулирование AST исходного кода не очень распространено, потому что:

  • кодогенерацию трудно верифицировать;

  • дерево разбора кажется сложным, непонятным и пугает.

Как раз на использовании AST в быту мы и остановимся.

Go- и JS-разработчик Открытой мобильной платформы Дима Смотров рассказал, как писать кодогенераторы в Go и оптимизировать работу над микросервисами с помощью создания инструмента для генерации шаблонного кода.Статья составлена на основе выступления Димы на GopherCon Russia 2020.

О продуктах и компонентах на Go

Наша команда разрабатывает мобильную ОС Аврора, SDK и экосистему приложений под неё, доверенную среду исполнения Аврора ТЕЕ, систему по управлению корпоративной мобильной инфраструктурой Аврора Центр, включающую несколько коробочных продуктов и компонентов.

Группа Дмитрия, в частности, работает над продуктом Аврора Маркет, который обеспечивает управление дистрибуцией приложений. Его бэкенд полностью написан на Go.

В Go принято отдавать предпочтение явному программированию (explicit) в противовес неявному (implicit). Это помогает новым разработчикам легче начинать работать над существующими проектами. Но по пути от неявного программирования к явному можно легко заблудиться и забрести в дебри дубляжа кода, а дубляж кода в дальнейшем превратит поддержку проекта в ад.

Чтобы этого избежать, код выносят в отдельные модули. Но как насчёт кода, который пишется специально для каждого микросервиса и не может быть вынесен в модуль? Например, код репозитория для работы с базой данных. Этот код есть в каждом микросервисе, выглядит примерно одинаково, но он разный и не дублируется. Не хочется писать шаблонный код, который потом придётся ещё и поддерживать во всех микросервисах.

Кодогенерация официальный инструмент от авторов Go

Для решения шаблонных задач можно использовать метапрограммирование это разработка программ, которые создают программы перед этапом компиляции или изменяют их во время выполнения. Этот метод относится к неявному программированию.

И хотя в Go принято отдавать предпочтение явному программированию, разработчики предоставили инструменты для метапрограммирования, такие как кодогенерация ($go help generate) и Reflection API. Reflection API используется на этапе выполнения программы, кодогенерация перед этапом компиляции. Reflection API увеличивает время работы программы. Пример: инструмент для кодирования и декодирования JSON из стандартной библиотеки Go использует Reflection API. Взамен ему сообществом были рождены такие альтернативы, как easyjson, который с помощью кодогенерации кодирует и декодирует JSON в 5 раз быстрее.

Так как кодогенерация неявное программирование, она недооценивается сообществом Go, хотя и является официальным инструментом от создателей этого языка программирования. Поэтому в интернете немного информации о написании кодогенераторов на Go. Но всё же на Хабре примеры есть: 1 и 2.

При разработке микросервисов есть много похожего шаблонного кода, который нужно писать в каждом микросервисе. Например, код репозитория по работе с базой данных. Мы создали кодогенераторы для того, чтобы разработчики не тратили время на написание этого шаблонного кода и могли сфокусироваться на решении задач, относящихся к дизайну кода и предметной области бизнеса. Команда использует кодогенераторы и для сокращения времени на создание новых микросервисов. Это позволяет не ограничивать разработчика в принятии архитектурных решений, так как создание нового микросервиса не влияет на трудоёмкость выполнения задачи.

Пример дублирующего кода:

type UserRepository struct{ db *gorm.DB }func NewRepository(db *gorm.DB) UserRepository {    return UserRepository{db: db}}func (r UserRepository) Get(userID uint) (*User, error) {    entity := new(User)    err := r.db.Limit(limit: 1).Where(query: "user_id = ?", userID).Find(entity).Error    return entity, err}func (r UserRepository) Create(entity *User) error {    return r.db.Create(entity).Error}func (r UserRepository) Update(entity *User) error {    return r.db.Model(entity).Update(entity).Error}func (r UserRepository) Delete(entity *User) error {    return r.db.Delete(entity).Error}

Про удачные кодогенераторы

Из примеров написанных и удачно используемых в нашей команде кодогенераторов хотим подробнее рассмотреть генератор репозитория по работе с базой данных. Нам нравится переносить опыт из одного языка программирования в другой. Так, наша команда попыталась перенести идею генерации репозиториев по работе с базой данных из Java Spring (https://spring.io/).

В Java Spring разработчик описывает интерфейс репозитория, исходя из сигнатуры метода автоматически генерируется реализация в зависимости от того, какой бэкенд для базы данных используется: MySQL, PostgreSQL или MongoDB. Например, для метода интерфейса с сигнатурой FindTop10WhereNameStartsWith (prefix string) автоматически генерируется реализация метода репозитория, которая вернёт до 10 записей из базы данных, имя которых начинается с переданного в аргументе префикса.

О нюансах и траблах внедрения кодогенератора

Существует парадигма Monolith First, когда пишут первую версию как монолит, а потом распиливают на микросервисы. На заре новой версии проекта, когда все команды должны были разбить монолит на микросервисы, мы решили написать свой генератор, который:

  • позволит вводить в систему новые микросервисы с меньшими усилиями, чем при его создании вручную (копируя предыдущий и удаляя лишнее);

  • сократит время на код-ревью за счёт общего шаблона для генерируемых микросервисов;

  • сократит время на будущие обновления одинакового кода микросервисов (main, инфрастуктура, etc).

Для разработки микросервисов командами было принято решение использовать go-kit. За основу мы взяли один из популярных существующих кодогенераторов для go-kit и стали его дорабатывать под наши требования для микросервисов. Он был написан с использованием не очень удобной библиотеки, которая использовала промежуточные абстракции для генерации кода Go. Код получался громоздким и трудным для восприятия и поддержки. В будущих версиях мы отказались от такого подхода и начали генерировать код Go с помощью шаблонов Go. Это позволило нам писать тот же самый код без каких-либо промежуточных абстракций. За пару недель нашей командой был написан прототип. А ещё через месяц был написан кодогенератор для go-kit, который буквально умел делать всё.

Разработчик описывает интерфейс go-kit-сервиса, а кодогенератор генерирует сразу всё, что для сервиса нужно:

  • CRUD-эндпоинты и REST-, gRPC- и NATS-транспорты;

  • репозиторий для работы с базой данных с возможностью расширять интерфейс репозитория;

  • main для всех go-kit-сервисов.

После того как кодогенератор был закончен, началось его внедрение. Мы сразу же столкнулись с проблемами. Разработчики компании неохотно принимали кодогенератор. Он генерировал слишком много кода, который нужно было ревьюить и перерабатывать. Сначала команды помогали исправлять генерируемый код, но подошло время уже разрабатывать сами микросервисы, а не кодогенератор для генерации сервисов. В итоге одни команды продолжали использовать кодогенератор, обходя его баги, а другие начали писать микросервисы без кодогенератора. Мы получили сегментацию сервисов. Когда инфраструктура менялась, изменения давались нам очень тяжело.

Позже наша команда работала над проектом, где нужно было больше заниматься адаптацией существующих Open-Source-продуктов под наши требования, чем разработкой новых микросервисов. Мы больше не могли заниматься кодогенератором, так как не использовали его активно в работе. А впоследствии мы тоже начали копировать сервисы вместе со всеми их недостатками.

Когда наша команда вернулась к разработке своего основного продукта, мы провели ретроспективу кодогенератора и поняли, в чём была основная проблема. Кодогенераторы, которые генерируют ВСЁ, сложно внедрять и поддерживать.

  • Кодогенератор генерировал слишком много кода.

  • Весь код нужно было ревьювить и перерабатывать.

  • Только часть команд решила пользоваться кодогенератором.

  • Получили сегментацию микросервисов.

Мы исправили эту проблему. Сейчас кодогенератор разбили на несколько мелких, у каждого из которых своя собственная ответственность. Разработчики сами выбирают, какие из них использовать в своих продуктах, а какие нет.

Как же всё-таки генерировать Go-код

Можно просто использовать шаблоны. Можно написать шаблон и начинить его параметрами, на это вполне способны продвинутые редакторы текста. Можно использовать неинтерактивные редакторы sed или awk, порог входа круче, зато лучше поддаётся автоматизации и встраивается в производственный конвейер. Можно использовать специфические инструменты рефакторинга Go из пакета golang.org/x/tools/cmd, а именно gorename или eg. А можно воспользоваться пакетом text/template из стандартной библиотеки решение достаточно гибкое, человекочитаемое (в отличие от sed), удобно интегрируется в pipeline и позволяет оставаться в среде одного языка.

И всё же для конвейерной обработки этого маловато: требует существенного вмешательства оператора.

Можно пойти по проторённому пути: gRPC, Protobuf, Swagger. Недостатки подхода:

  • привязывает к gRPC, Protobuf;

  • не заточен конкретно под Go, а, напротив, требует изучения и внедрения новых, сторонних абстракций и технологий.

Чтобы остаться в родных пенатах воспользуемся средствами из стандартной библиотеки пакетами go/:

  • go/ast декларирует типы дерева разбора;

  • go/parser разбирает исходный код в эти типы;

  • go/printer выливает AST в файл исходного кода;

  • go/token обеспечивает привязку дерева разбора к файлу исходного кода.

Можно сделать шаблон, разобрать его AST и манипулировать этим деревом, подгоняя под нужный вид. Основной недостаток метода плохая читаемость манипуляций с AST и трудоёмкость верификации.

Можно вытащить из AST параметры, вмонтировать в шаблон и всё. Недостаток разрозненные обработки неудобно собирать в конвейер.

Поэтому выбран такой алгоритм кодогенерации:

  1. Разбираем AST исходного файла.

  2. Создаём пустое AST для генерируемого файла.

  3. Генерируем код из шаблонов Go (template/text).

  4. Разбираем AST сгенерированного кода.

  5. Копируем узлы AST из сгенерированного кода в AST генерируемого файла.

  6. Печатаем и сохраняем AST генерируемого файла в файл.

Чтобы было понятней и не пугала загадочная аббревиатура AST дерево разбора Hello World:

package mainimport "fmt"func main() {    fmt.Println("Hello, World!")}

...выглядит вот так:

...или вот так, напечатанное специализированным принтером ast.Print():

ast.Print
0  *ast.File {1  .  Package: 2:12  .  Name: *ast.Ident {3  .  .  NamePos: 2:94  .  .  Name: "main"5  .  }6  .  Decls: []ast.Decl (len = 2) {7  .  .  0: *ast.GenDecl {8  .  .  .  TokPos: 4:19  .  .  .  Tok: import10  .  .  .  Lparen: -11  .  .  .  Specs: []ast.Spec (len = 1) {12  .  .  .  .  0: *ast.ImportSpec {13  .  .  .  .  .  Path: *ast.BasicLit {14  .  .  .  .  .  .  ValuePos: 4:815  .  .  .  .  .  .  Kind: STRING16  .  .  .  .  .  .  Value: "\"fmt\""17  .  .  .  .  .  }18  .  .  .  .  .  EndPos: -19  .  .  .  .  }20  .  .  .  }21  .  .  .  Rparen: -22  .  .  }23  .  .  1: *ast.FuncDecl {24  .  .  .  Name: *ast.Ident {25  .  .  .  .  NamePos: 6:626  .  .  .  .  Name: "main"27  .  .  .  .  Obj: *ast.Object {28  .  .  .  .  .  Kind: func29  .  .  .  .  .  Name: "main"30  .  .  .  .  .  Decl: *(obj @ 23)31  .  .  .  .  }32  .  .  .  }33  .  .  .  Type: *ast.FuncType {34  .  .  .  .  Func: 6:135  .  .  .  .  Params: *ast.FieldList {36  .  .  .  .  .  Opening: 6:1037  .  .  .  .  .  Closing: 6:1138  .  .  .  .  }39  .  .  .  }40  .  .  .  Body: *ast.BlockStmt {41  .  .  .  .  Lbrace: 6:1342  .  .  .  .  List: []ast.Stmt (len = 1) {43  .  .  .  .  .  0: *ast.ExprStmt {44  .  .  .  .  .  .  X: *ast.CallExpr {45  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {46  .  .  .  .  .  .  .  .  X: *ast.Ident {47  .  .  .  .  .  .  .  .  .  NamePos: 7:248  .  .  .  .  .  .  .  .  .  Name: "fmt"49  .  .  .  .  .  .  .  .  }50  .  .  .  .  .  .  .  .  Sel: *ast.Ident {51  .  .  .  .  .  .  .  .  .  NamePos: 7:652  .  .  .  .  .  .  .  .  .  Name: "Println"53  .  .  .  .  .  .  .  .  }54  .  .  .  .  .  .  .  }55  .  .  .  .  .  .  .  Lparen: 7:1356  .  .  .  .  .  .  .  Args: []ast.Expr (len = 1) {57  .  .  .  .  .  .  .  .  0: *ast.BasicLit {58  .  .  .  .  .  .  .  .  .  ValuePos: 7:1459  .  .  .  .  .  .  .  .  .  Kind: STRING60  .  .  .  .  .  .  .  .  .  Value: "\"Hello, World!\""61  .  .  .  .  .  .  .  .  }62  .  .  .  .  .  .  .  }63  .  .  .  .  .  .  .  Ellipsis: -64  .  .  .  .  .  .  .  Rparen: 7:2965  .  .  .  .  .  .  }66  .  .  .  .  .  }67  .  .  .  .  }68  .  .  .  .  Rbrace: 8:169  .  .  .  }70  .  .  }71  .  }72  .  Scope: *ast.Scope {73  .  .  Objects: map[string]*ast.Object (len = 1) {74  .  .  .  "main": *(obj @ 27)75  .  .  }76  .  }77  .  Imports: []*ast.ImportSpec (len = 1) {78  .  .  0: *(obj @ 12)79  .  }80  .  Unresolved: []*ast.Ident (len = 1) {81  .  .  0: *(obj @ 46)82  .  }83  }

Хватит трепаться, покажите код

В целом задача сводится к тому, чтобы разработчик микросервиса мог описать свои данные обычным образом в стандартном синтаксисе Go, только добавить в комментариях директивы процессинга:

//repogen:entitytype User struct {    ID              uint `gorm:"primary_key"`    Email           string    PasswordHash    string}

...запустить go generate и получить вот такой файл с готовой обвязкой для работы с DB, в котором прописаны методы именно для его типа данных User:

User
type UserRepository struct{db *gorm.DB}func NewRepository(db *gorm.DB) UserRepository {    return UserRepository{db: db}}func (r UserRepository) Get(userID uint) (*User, error) {    entity := new(User)    err := r.db.Limit(limit: 1).Where(query: "user_id = ?", userID).Find(entity).Error    return entity, err}func (r UserRepository) Create(entity *User) error {    return r.db.Create(entity).Error}func (r UserRepository) Update(entity *User) error {    return r.db.Model(entity).Update(entity).Error}func (r UserRepository) Delete(entity *User) error {    return r.db.Delete(entity).Error}

Напишем такой генератор прямо здесь и сейчас, не применяя готовых решений, а ограничившись стандартной библиотекой.

Кода потребовалось не очень много, поэтому он представлен одним листингом, чтобы не терялась общая картина. Пояснения даны в комментариях, в стиле literate programming.

Вот модель, для которой нам нужно сгенерировать методы работы с DB. В комментариях видны директивы:

  • go:generate repogen для команды go generate на запуск процессора repogen;

  • repogen:entity помечает цель для процессора repogen;

  • и тег поля структуры gorm:"primary_key" для процессора gorm помечает первичный ключ в таблице DB.

package gophercon2020//go:generate repogen//repogen:entitytype User struct {    ID              uint `gorm:"primary_key"`    Email           string    PasswordHash    string}

Вот код, собственно, процессора repogen:

Процессор repogen
package mainimport (    "bytes"    "go/ast"    "go/parser"    "go/printer"    "go/token"    "golang.org/x/tools/go/ast/inspector"    "log"    "os"    "text/template")//Шаблон, на основе которого будем генерировать//.EntityName, .PrimaryType  параметры,//в которые будут установлены данные, добытые из AST-моделиvar repositoryTemplate = template.Must(template.New("").Parse(`package mainimport (    "github.com/jinzhu/gorm")type {{ .EntityName }}Repository struct {    db *gorm.DB}func New{{ .EntityName }}Repository(db *gorm.DB) {{ .EntityName }}Repository {    return {{ .EntityName }}Repository{ db: db}}func (r {{ .EntityName }}Repository) Get({{ .PrimaryName }} {{ .PrimaryType}}) (*{{ .EntityName }}, error) {    entity := new({{ .EntityName }})    err := r.db.Limit(1).Where("{{ .PrimarySQLName }} = ?", {{ .PrimaryName }}).Find(entity).Error()    return entity, err}func (r {{ .EntityName }}Repository) Create(entity *{{ .EntityName }}) error {    return r.db.Create(entity).Error}func (r {{ .EntityName }}Repository) Update(entity *{{ .EntityName }}) error {    return r.db.Model(entity).Update.Error}func (r {{ .EntityName }}Repository) Update(entity *{{ .EntityName }}) error {    return r.db.Model(entity).Update.Error}func (r {{ .EntityName }}Repository) Delete(entity *{{ .EntityName }}) error {    return r.db.Delete.Error}`))//Агрегатор данных для установки параметров в шаблонеtype repositoryGenerator struct{    typeSpec    *ast.TypeSpec    structType  *ast.StructType}//Просто helper-функция для печати замысловатого ast.Expr в обычный stringfunc expr2string(expr ast.Expr) string {    var buf bytes.Buffer    err := printer.Fprint(&buf, token.NewFileSet(), expr)    if err !- nil {        log.Fatalf("error print expression to string: #{err}")    return buf.String()}//Helper для извлечения поля структуры,//которое станет первичным ключом в таблице DB//Поиск поля ведётся по тегам//Ищем то, что мы пометили gorm:"primary_key"func (r repositoryGenerator) primaryField() (*ast.Field, error) {    for _, field := range r.structType.Fields.List {        if !strings.Contains(field.Tag.Value, "primary")            continue        }        return field, nil    }    return nil, fmt.Errorf("has no primary field")}//Собственно, генератор//оформлен методом структуры repositoryGenerator,//так что параметры передавать не нужно://они уже аккумулированы в ресивере метода r repositoryGenerator//Передаём ссылку на ast.File,//в котором и окажутся плоды трудовfunc (r repositoryGenerator) Generate(outFile *ast.File) error {    //Находим первичный ключ    primary, err := r.primaryField()    if err != nil {        return err    }    //Аллокация и установка параметров для template    params := struct {        EntityName      string        PrimaryName     string        PrimarySQLName  string        PrimaryType     string    }{        //Параметры извлекаем из ресивера метода        EntityName      r.typeSpec.Name.Name,        PrimaryName     primary.Names[0].Name,        PrimarySQLName  primary.Names[0].Name,        PrimaryType     expr2string(primary.Type),    }    //Аллокация буфера,    //куда будем заливать выполненный шаблон    var buf bytes.Buffer    //Процессинг шаблона с подготовленными параметрами    //в подготовленный буфер    err = repositoryTemplate.Execute(&buf, params)    if err != nil {        return fmt.Errorf("execute template: %v", err)    }    //Теперь сделаем парсинг обработанного шаблона,    //который уже стал валидным кодом Go,    //в дерево разбора,    //получаем AST этого кода    templateAst, err := parser.ParseFile(        token.NewFileSet(),        //Источник для парсинга лежит не в файле,        "",        //а в буфере        buf.Bytes(),        //mode парсинга, нас интересуют в основном комментарии        parser.ParseComments,    )    if err != nil {        return fmt.Errorf("parse template: %v", err)    }    //Добавляем декларации из полученного дерева    //в результирующий outFile *ast.File,    //переданный нам аргументом    for _, decl := range templateAst.Decls {        outFile.Decls = append(outFile.Decls, decl)    }    return nil}func main() {    //Цель генерации передаётся переменной окружения    path := os.Getenv("GOFILE")    if path == "" {        log.Fatal("GOFILE must be set")    }    //Разбираем целевой файл в AST    astInFile, err := parser.ParseFile(        token.NewFileSet(),        path,        src: nil,        //Нас интересуют комментарии        parser.ParseComments,    )    if err != nil {        log.Fatalf("parse file: %v", err)    }    //Для выбора интересных нам деклараций    //используем Inspector из golang.org/x/tools/go/ast/inspector    i := inspector.New([]*ast.File{astInFile})    //Подготовим фильтр для этого инспектора    iFilter := []ast.Node{        //Нас интересуют декларации        &ast.GenDecl{},    }    //Выделяем список заданий генерации    var genTasks []repositoryGenerator    //Запускаем инспектор с подготовленным фильтром    //и литералом фильтрующей функции    i.Nodes(iFilter, func(node ast.Node, push bool) (proceed bool){        genDecl := node.(*ast.GenDecl)        //Код без комментариев не нужен,        if genDecl.Doc == nil {            return false        }        //интересуют спецификации типов,        typeSpec, ok := genDecl.Specs[0].(*ast.TypeSpec)        if !ok {            return false        }        //а конкретно структуры        structType, ok := typeSpec.Type.(*ast.StructType)        if !ok {            return false        }        //Из оставшегося        for _, comment := range genDecl.Doc.List {            switch comment.Text {            //выделяем структуры, помеченные комментарием repogen:entity,            case "//repogen:entity":                //и добавляем в список заданий генерации                genTasks = append(genTasks, repositoryGenerator{                    typeSpec: typeSpec,                    structType: structType,                })            }        }        return false    })    //Аллокация результирующего дерева разбора    astOutFile := &ast.File{        Name: astInFile.Name,    }    //Запускаем список заданий генерации    for _, task := range genTask {        //Для каждого задания вызываем написанный нами генератор        //как метод этого задания        //Сгенерированные декларации помещаются в результирующее дерево разбора        err = task.Generate(astOutFile)        if err != nil {            log.Fatalf("generate: %v", err)        }    }    //Подготовим файл конечного результата всей работы,    //назовем его созвучно файлу модели, добавим только суффикс _gen    outFile, err := os.Create(strings.TrimSuffix(path, ".go") + "_gen.go")    if err != nil {        log.Fatalf("create file: %v", err)    }    //Не забываем прибраться    defer outFile.Close()    //Печатаем результирующий AST в результирующий файл исходного кода    //Печатаем не следует понимать буквально,    //дерево разбора нельзя просто переписать в файл исходного кода,    //это совершенно разные форматы    //Мы здесь воспользуемся специализированным принтером из пакета ast/printer    err = printer.Fprint(outFile, token.NewFileSet(), astOutFile)    if err != nil {        log.Fatalf("print file: %v", err)    }}

Подводя итоги

Работа с деревом разбора в Go не требует сверхъестественных способностей. Язык предоставляет для этого вполне годный инструментарий. Кода получилось не слишком много, и он достаточно читаем и, надеемся, понятен. Высокой эффективности здесь добиваться нет нужды, потому что всё происходит ещё до стадии компиляции и на стадии выполнения издержек не добавляет (в отличие от reflect). Важнее валидность генерации и манипуляций с AST. Кодогенерация сэкономила нам достаточно времени и сил в написании и поддержке большого массива кода, состоящего из повторяющихся паттернов (микросервисов). В целом кодогенераторы оправдали затраты на своё изготовление. Выбранный pipeline показал себя работоспособным и прижился в производственном процессе. Из стороннего опыта можем рекомендовать к использованию:

  • dst (у которого лучше разрешение импортируемых пакетов и привязка комментариев к узлам AST, чем у go/ast из stdlib).

  • kit (хороший toolkit для быстрой разработки в архитектуре микросервисов. Предлагает внятные, рациональные абстракции, методики и инструменты).

  • jennifer (полноценный кодогенератор. Но его функциональность достигнута ценой применения промежуточных абстракций, которые хлопотно обслуживать. Генерация из шаблонов text/template на деле оказалась удобней, хоть и менее универсальной, чем манипулирование непосредственно AST с использованием промежуточных абстракций. Писать, читать и править шаблоны проще).

Набор узконаправленных генераторов оказался сподручней одного универсального и в применении, и в поддержке. Здесь допустима аналогия микросервисы vs монолит. Делитесь опытом и мнениями в комментариях.

Подробнее..

От эскиза до релиза пайплайн регулярного создания контента на примере идеи для оружия от игрока

07.04.2021 20:14:16 | Автор: admin

Огромное количество игр построено на сервисной поддержке, будь то тактический шутер Rainbow Six Siege или большая ролевая World of Warcraft. Игроков постоянно вовлекают ивентами, игровыми режимами, картами, персонажами или перками. Но когда в проекте уже сотни и тысячи единиц контента, а релизы ежемесячно это может стать проблемой для разработчиков.

В какой-то момент мы тоже с этим столкнулись в мобильном PvP-шутере и пришлось искать решение. В итоге полностью пересмотрели пайплайн создания контента и оптимизировали процессы так, чтобы одна гугл-таблица заменила нам таск-трекеры. О нашем опыте на проекте со 170 млн инсталлов и 1 млн DAU подробно расскажу под катом.

В нашем пайплайне единица контента движется по линии этапов и в любой момент если происходит изменение концепта, модели или чего-то еще, она может вернуться в нужную точку и заново пройти по всем шагам. Так не появляются непредсказуемые и неутвержденные вещи все работают только над тем, что попадет в игру.

Весь пайплайн разработки контента делится на несколько этапов:

  1. Разработка идеи. Ищем, отсеиваем и собираем лучшие идеи контента.

  2. Создание 2D-концепта. Рисуем эскизы и концепты.

  3. Создание 3D-концепта. Превращаем концепты в 3D-модели.

  4. Оптимизация 3D-модели. Оптимизируем и готовим к анимации.

  5. Анимирование. Делаем риггинг и оживляем контент.

  6. Подключение к проекту. Настраиваем механики, добавляем эффекты и звук.

  7. Тестирование. Проводим плейтесты, даем фидбек и устраняем баги.

  8. Релиз и сбор статистики. Выпускаем в сторы, работаем с аудиторией, метриками и, если остались, отлавливаем баги.

  9. Ревью контента. Следим за игровым балансом и проверяем параметры.

Контент проходит каждый этап разработки, но если в работе параллельно несколько десятков единиц оружия, то рано или поздно начнутся проблемы: кто-то одновременно будет моделировать одно и то же, что-то потеряется из виду или пропустит нужный этап. Решить их, как оказалось, можно буквально одной гугл-таблицей.

Чтобы не забегать вперед, последовательно пройдем по всему пайплайну: расскажу о каждом этапе, в каких инструментах работаем, как оптимизируем контент, подключаем его к проекту, тестируем и взаимодействуем друг с другом, а также почему важно не ограничивать свободу творчества.

Сейчас над нашим мобильным шутером Pixel Gun 3D работают более 80 человек. У проекта свыше тысячи единиц контента, сотня карт, десяток онлайн-режимов (включая батлрояль на двух огромных картах) и даже сюжетная кампания. Все разберем на его примерах и кейсах.

1. Разработка идеи

Добавление нового контента в игру одна из главных составляющих LiveOps. Проект всегда улучшается на основе аналитики и тестов, большой пласт работы составляет общее улучшение метрик проекта, а также работа с комьюнити. Но обычно игроки обращают внимание именно на контент.

В любых играх-сервисах к каждому обновлению игры принято готовить цикличный пак контента (сейчас это называют сезоном). Он состоит из тематики, сюжета, батлпасса, пушек, скинов, карт и много чего еще. Например, недавно Activision выпустила большой апдейт Call of Duty Black Ops: Cold War, в котором главной темой стал Зомби-ивент.

Зомби-режим Outbreak в Call of Duty Black Ops: Cold WarЗомби-режим Outbreak в Call of Duty Black Ops: Cold War

Сезоны в Pixel Gun 3D продуманы на 2-3 вперед. Составляется фиксированная сетка, где расписывается, какая тема за какой следует, и что будет внутри: механики, новые режимы, карты и прочее. Контент должен максимально отличаться от представленного в предыдущем сезоне так он намного привлекательнее для игроков.

Поэтому нужно постоянно находить и генерировать цепляющие идеи. Вдохновение можно встретить и в реальной жизни, но если говорить о проверенных способах, то глобально есть три направления:

  1. Анализ популярных трендов на рынке.

  2. Проведение арт-конкурсов среди комьюнити.

  3. Разработка уникального сеттинга для привлечения внимания.

Среди популярных трендов, например, все еще киберпанк, но, вероятно, пик интереса к нему прошел с релизом долгостроя CD Projekt RED. Еще есть ежегодные праздники, в феврале отмечается Китайский Новый год ивент, когда все добавляют фонарики, красные скины и атрибутику с текущим знаком зодиака. Или 14 февраля, где на пару дней в играх появляются сердечки, купидоны и тому подобное.

Трендом может стать не только сеттинг или отдельно взятый праздник, но и игровой режим. Например, Imposter по мотивам Among Us. Про него выпустим отдельный материал расскажем, как добавляли режим, зачем вообще разработчикам тренды, откуда они берутся и как их применять в своем проекте.

Другая хорошая практика проводить арт-конкурсы среди комьюнити. Игроки предлагают собственные варианты оружия или персонажей, после чего сами же голосуют за лучшие, которые затем попадают в игру. Это вообще win-win ситуация: разработчики собирают новые идеи (иногда присылают даже готовые концепты), а игроки призы, признание, социализацию и дополнительную вовлеченность.

Подобные активности проводятся у нас с 2014 года, когда в игре было порядка 30 пушек. За это время механику проведения конкурсов особо не меняли, поэтому опытные игроки готовятся к ним заранее, а потом отправляют практически готовое ТЗ с описанием механик, параметрами DPS и даже анимациями. Авторы потом получают свои пушки уже в игре вместе с призовой валютой.

Хоть это и виралит, в индустрии такое встретишь не часто, обычно потому, что производство контента занимает много времени и ресурсов.

Но вполне можно выстроить быстрый и эффективный пайплайн, который позволит разработать единицу контента за считанные дни. Здесь помогает наш гибкий сеттинг игры, где миксуются любые темы: от реалистичных калашей до магических посохов. Поэтому проблем с идеями не возникает.

Способ посложнее придумать уникальную тематику под внутренний праздник или выдуманный ивент, если есть предположения, что она понравится игрокам. Например, мы выпустили сезон разработчиков, в котором сделали карту Офис. Туда добавили кучу отсылок на наш настоящий офис в Ростове-на-Дону, частично воссоздали интерьеры и дали в них побегать игрокам.

Офис Lightmap в Ростове-на-ДонуОфис Lightmap в Ростове-на-ДонуКарта Офис в Pixel Gun 3DКарта Офис в Pixel Gun 3D

Даже если возникают сложности с тематикой, то всегда есть место для экспериментов: за основу взять классическую историю (например, путешествие к центру Земли), добавить в нее новых деталей или скрестить с чем-нибудь еще.

Отобранные идеи обсуждаются ведущим геймдизайнером и продакт-овнером. ГД готовит мини-питч по каждой: почему она должна понравиться игрокам, в чем будет ее фишка и так далее.

Лучшие идеи попадают в концепт-док с общей канвой обновления: пишется сюжет, раскрываются детали лора и прикрепляются референсы. В дальнейшем это повлияет на мотивацию игроков, обоснование игровых режимов и прочего.

Скриншот концепт-докаСкриншот концепт-дока

Затем документ передается в концепт-отдел, где стартует реализация.

2. Создание 2D-концепта

Начиная с этого момента и дальше, арт-отдел может на свой вкус улучшать визуал любой составляющей единицы контента (придерживаясь общей тематики, разумеется). Важно оставлять простор для творчества, чтобы избежать выгорания.

Бывают исключения, когда нужно обыграть конкретный мем или тренд тогда ТЗ будет жесткое. В остальных случаях главный критерий один должно быть интересно.

Эскизы от руки практически не рисуются, обычно только, когда нужно придумать что-то совсем нестандартное. Из-за особенностей пиксельной стилистики художникам удобнее работать сразу в Photoshop: открыть картинку 100100 и прямо в ней попиксельно рисовать.

Создание 2D-концепта в PhotoshopСоздание 2D-концепта в Photoshop

Здесь к художникам параллельно подключается кор-команда она придумывает механики и свойства будущему контенту. Например, лид геймдизайнер в концепт-доке предполагает, что снайперка должна пробивать стены на основе этого составляется набор фич.

Затем думают над способами реализации (самостоятельно готовыми инструментами или привлечением программиста), какие параметры прописывать и так далее на протяжении всего пайплайна важно следить за изменениями, которые могут на это повлиять.

Кроме того, при концептировании художники сразу прикидывают уникальные моменты по анимированию. Если нужно что-то быстро переделать или заменить, 2D-художники примеряют на себя роль специалистов по 3D, что помогает параллельно прокачивать скиллы. Те, кто не знает анимацию потихоньку учат ее основы; кто не работал с текстурами пробует текстурировать, и так далее. Разумеется, у каждого своя основная специализация, но при этом любой умеет выдавить, затекстурировать и анимировать, например, оружие. Так не только интереснее работать, но и позволяет специалистам развиваться в разных направлениях.

Каждый сотрудник знает весь пайплайн, поэтому когда контент доходит до нужного этапа многие вещи уже продуманы и учтены. Здесь же формируется сетка по категориям и рекомендованным механикам, которая основывается на анализе продаж прошлых апдейтов (чтобы от сезона к сезону контент не повторялся).

Но вернемся к 2D-концептам. Когда художники заканчивают работу, то на выходе получается примерно такое полотно:

Полотно концептовПолотно концептов

Из десятков вариантов выбираются наиболее удачные и дорабатываются. Для примера возьмем эскиз участника арт-конкурса среди комьюнити и сделаем из него оружие.

Эскиз (слева) и готовый 2D-концепт (справа)Эскиз (слева) и готовый 2D-концепт (справа)

Все пушки должны быть выдержаны в одном стиле. Поэтому выбранный концепт обязательно калибруется:

  • Настраивается плотность пикселей чтобы концепт не был слишком детальным или, наоборот, грубым.

  • Дорабатывается цвет чтобы не было плавных градиентов (из-за специфики графики используется по 3-4 оттенка на 3-4 цвета для одной пушки).

  • Подгоняется по цветовой палитре чтобы ничего не выбивалось (результат сравнивается с рефами по визуалу и будущим эффектам).

Когда модель откалибрована создается подробное ТЗ. Оно, как правило, включает сюжет сезона, связь с предыдущим, лор и описание контента (2D-концепт, механики, свойства, референсы по визуальному стилю и анимациям).

Этот документ помогает на протяжении всего пайплайна. Его читает каждый отдел все знают общую концепцию сезона, поэтому большинство проблем просто не возникает. К тому же, это эффективно: если не ограничивать никого жестким ТЗ, то часто можно получить результат, который в разы превзойдет первоначальные ожидания.

Пример ТЗ на оружие:

Название, класс, тег: Maximum Cruelty (Максимальная Жестокость), Heavy, Weapon1282.
Механика: Заряжаем выстрел, создавая шар перед дулом (пушка раскрывается). После этого происходит мгновенный взрыв в точке прицела. Убитые цели взрываются, нанося дополнительный урон, но уже в меньше области (возможна цепная реакция).
Свойства: Charge Shot / Area Damage / Targets Explode.
Оформление: Нижний подвес (держим, как миниган), энергия внутри пушки постоянно светится (ставим дополнительный материал для свечения в темноте). При перезарядке энергия тухнет до установки батареи.

Референсы к ТЗ на оружиеРеференсы к ТЗ на оружие

Концепты выбраны, документ составлен, вижен синхронизирован. Значит, можно переходить к созданию 3D-модели.

3. Создание 3D-концепта

Утвержденные 2D-концепты попадают в руки 3D-отдела.

Раньше мы использовали 3ds Max, но с ним было много проблем, особенно у новичков. Очень утрированный пример: делали модель не 50 пикселей, а 48 с половиной потом текстура в 50 пикселей просто не ложилась как надо.

Сейчас перешли на два воксельных редактора: MagicaVoxel и Qubicle (но к 3ds Max еще вернемся). Моделирование в них происходит не сложными формами, а вокселями (что идеально подходит для нашей стилистики). Можно быстро и удобно посмотреть модель в объеме с разных ракурсов и найти проблемные места. В итоге скорость значительно выросла теперь 3-4 модели проверяются всего за час.

Воксельные редакторы сразу дают правильную фигуру на выходе получается 3D-модель с текстурой (ее потом проще оптимизировать и запечь).

У нас есть собственный набор правил и ограничений по работе с 3D-моделью, но из-за специфики графики от них иногда приходится отступать. Начинается нетривиальный процесс придания формы, чтобы модель была не слишком простой, но и не слишком сложной.

Выдавленный 2D-концептВыдавленный 2D-концепт

Не всегда все идет гладко. Иногда пушка в 2D-концепте выглядит круто, а в 3D вообще не цепляет. Именно воксельный редактор помогает ускорить процесс и понять, что делать с моделью: либо довести до ума, либо вернуть на этап 2D-концепта.

Бывает, что концепт вообще полностью меняем. Например, сколько бы ни тестили пушку, стреляющую щитом с топорами по типу бумеранга, играть с ней было скучно. Она просто не цепляла.

Модель оружия Метатель Топоров и рефыМодель оружия Метатель Топоров и рефы

В итоге сделали вместо неё Мьёльнир с совершенно другой механикой и получили совсем другие эмоции от игры.

Модель оружия Молот Тора и рефыМодель оружия Молот Тора и рефы

4. Оптимизация 3D-модели

От 3ds Max, конечно, полностью не отказались. Его вместе с Blender и Maya используем дальше для оптимизации и анимации. На этом этапе максимально упрощаем оружие, скашиваем углы и правим ту геометрию, которую не позволял менять воксельный редактор.

Обязательно проводим габаритный тест модели с персонажем, начинаем думать, как будем нарезать, сколько костей добавлять и так далее. А еще обозначаем дополнительные требования к оформлению здесь может появиться крутая идея, которая сильно поднимет привлекательность для игрока, но сохранит первичную концепцию и стиль.

Проверка на габариты с персонажемПроверка на габариты с персонажем

Крайне важно правильно оптимизировать модель. У нас почти в любом мультиплеерном режиме 10 игроков, у каждого есть питомцы, шапки, броня, пушки, гаджеты; они стреляют, вокруг разлетаются частицы, взрываются эффекты и так далее. При этом суммарно мобильный девайс тянет до 200 тысяч полигонов если ошибемся с одной пушкой и сделаем ее на 5000 полигонов и она станет популярной, то производительности конец.

Чтобы этого не происходило, есть ограничения. Например, для оружия рекомендовано 1000 полигонов. В редких случаях на крутую пушку можем выделить 1500-2000 полигонов.

Уменьшить число полигонов, сохранив форму, пиксельность и кубичность можно, но приходится выкручиваться и идти на хитрости. В пушке из примера упростили рога снизу, а верх сделали трапециями. Без этого на модели было бы 2500 полигонов.

Оптимизированная модельОптимизированная модель

После запекается текстура. Она должна быть правильной квадратной формы, и чем меньше, тем лучше. Здесь пиксельная стилистика работает в плюс можно сделать небольшую текстуру 6464 или 128128 и вложить туда кучу информации.

Тот, кто занимается оптимизацией, заранее думает, какие объекты будут двигаться и что будет задействовано, чтобы впоследствии разделить эти объекты на отдельные меши и зариггать. Обязательно стараемся минимизировать количество костей (пушка, обойма, затвор, гильза и так далее) для аниматоров. У самых сложных пушек доходит до 20, но обычно укладываемся в 10 или меньше.

Здесь нужно сделать небольшое отступление и рассказать про еще одну важную особенность. Мы работаем с Git-системой, когда на сервере лежит проект, и все заливают туда изменения. Если два человека возьмут одну и ту же модель, сделают ее локально и начнут загружать произойдет конфликт версий. Чтобы этого не случилось, каждая единица контента закрепляется за одним сотрудником.

Для этого есть отдельная гугл-таблица с контент-планом, где отмечается, кто за что и на каком этапе отвечает. Если контент переходит дальше, то ответственный меняется. Никто ничего в работу не возьмет пока не проверит эту таблицу. В нее, помимо прочего, записывается и другая информация по мере появления: название, теги, описание, механики, свойства и все остальное.

Часть таблицы с контент-планомЧасть таблицы с контент-планом

Для удобства можно использовать цветовые маркеры и выделять ячейки:

  • оранжевый контент в работе;

  • зеленый готов к тестированию;

  • синий протестирован и готов к релизу.

Одна таблица заменила нам таск-трекеры, в ней же находится ссылка на подробное ТЗ. По сути, сезонный контент находится в рамках двух документов, что сильно упрощает всем жизнь. Особенно тем, кому нужно быстро влиться в рабочий процесс после отсутствия.

С организационным моментом разобрались, возвращаемся к разработке.

Когда кор-отдел читает описание пушки, то он сразу прикидывает тайминги, а когда 3D-модель уже практически готова и заапрувлена, то прописывает их в таблице. Каждая пушка в игре имеет точные значения скорости выстрелов и перезарядки к этому в том числе привязывается анимация.

5. Анимирование

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

Анимация делается с помощью тех же инструментов, что и оптимизация (3ds Max, Blender и Maya). Ее у нас несколько видов:

  • idle (когда персонаж стоит и ничего не делает);

  • стрельба;

  • перезарядка;

  • пустой магазин (стрельба без боеприпасов);

  • лоадинг (взятие пушки из арсенала);

  • анимация профайла (взаимодействие персонажа с пушкой в магазине: крутит, перезаряжает, полирует и прочее).

Анимация профайлаАнимация профайла

Последняя в списке самая длинная и сложная. Но приносит практическую пользу работает в качестве витрины и демонстрирует контент игроку со всех сторон. Ее обсуждаем отдельно, но делаем это в самом конце, чтобы не задерживать плейтесты.


Жестких условий по анимации обычно нет, есть рефы и тематика (но бывает и строго прописанное ТЗ). Главное техническое условие в анимировании попасть в тайминги, указанные кор-отделом. Это скорость перезарядки, скорость стрельбы и прочие параметры. Иногда приходится возвращаться к кор-отделу, когда в тайминги не укладывается клевая анимация.

Анимация стрельбыАнимация стрельбыАнимация перезарядкиАнимация перезарядки

Если есть жесткие критерии, то они будут заранее указаны в ТЗ. Например, ракету взрывать с эффектом фаербола, из которого разлетаются улыбки. Такие детали прописаны давно, и когда они доходят до своих этапов и нужных специалистов переносятся в таблицу с контент-планом и берутся в работу.

Иногда нужно сделать специфичное оформление. Например, кибернетический модуль, который раскладывается прямо из руки персонажа, потребовал включить дополнительную цикличную анимацию.






Или другой пример органическая пушка стала выглядеть гораздо лучше, когда к ней добавили анимацию глаза.


В конце этого этапа получается готовый к добавлению в проект контент. У пушки есть основное анимированная 3D-модель. Теперь осталось навести дополнительную красоту.

6. Подключение к проекту

Контент обязан выглядеть привлекательно не только в лобби, но и в геймплее. На это влияет все и сразу: арт, анимации, эффекты и звуки.

3D-художник, который занимался оптимизацией и анимацией пушки, продолжает с ней работать. Следующий шаг залить в Git и технически подключить к проекту, чтобы пушка полность работала в игре. Тонкие настройки механики он не выставляет, только приблизительные.

Когда все подключено, сообщает об этом геймдизайнеру по кору, FX-дизайнеру и саунд-дизайнеру. Дальше процесс такой:

  1. Саунд-дизайнер предварительно накидывает звуки, ориентируясь на концепт-док и ТЗ. Эффекты берет из библиотек звуков, комбинирует, тюнит, а при необходимости записывает сам. Креатив не ограничиваем, только, как и везде, задаем направление тематикой или описанием пушки, если она специфичная.

  2. Следом геймдизайнер настраивает механику: сколько пуль будет в обойме, какие тайминги и параметры. Если механика уже есть в игре, то к процессу вообще не подключается программист все делается арт-отделом самостоятельно. Если механика новая или старой чего-то не хватает тут без программиста уже не обойтись.

  3. Дальше модель забирает FX-дизайнер и в Unity добавляет эффекты, партиклы взрывов, выстрелов, вылет гильз и остальную красоту.

  4. Под конец пушка возвращается к саунд-дизайнеру, который на основе настроенных механик и эффектов корректирует звук.

К концу этапа получается готовая единица контента она замоделена, анимирована, настроена, озвучена и готова к плейтестам.

7. Тестирование

Pixel Gun 3D в этом году исполнится 8 лет. Это большой и сложный проект с сотнями механик и десятками режимов. И с большим легаси. Иногда новый дробовик может сломать кнопку паузы. Я, конечно, утрирую, но что-то подобное в теории возможно.

Поэтому важно иметь запас на тестирование. Если нет времени или появились сложности, то в первую очередь нужно браться за то, с чем точно проблем не будет. Например, пока 3D-шники моделят карты, 2D-концептеры перерисовывают проблемные пушки и апдейтят ТЗ. За один день так можно переделать 10-20 вариантов, утвердить и заново отправить в пайплайн.

Когда все готово начинаются первые плейтесты. Контрольная группа с участием арт-лидов, ГД-лида и продакт-овнера проверяет комфорт, качество визуала и геймплея со всем контентом из ближайшего релиза.

Обычно это первый и последний плейтест. После него формируется документ с правками и контент отправляется в нужный этап пайплайна на фиксы. Если правка по звуку, то доходит до саунд-дизайнера, он фиксит; по анимации к аниматору, и далее по списку. Но чаще всего правки графические: добавить лампочку, блестящий шейдер и подобные визуальные улучшалки. Все это полируется на финальной неделе и уходит в продакшн.

Откорректировать тайминг или поменять свойства довольно просто, самое неприятное менять модель. Это отбрасывает нас в точку получения 3D-концепта и нужно проверять все заново. Но если обнаруживается баг на релизной неделе, то у нас есть понятие красный коридор если все хорошо с анимацией, моделью, звуками, но есть мелкий баг, то пушка минует все ненужные этапы, фиксится, где необходимо и попадает сразу к тестировщикам.

Да, бывают случаи, когда нужно переделать пушку чуть ли не с нуля или заменить на другую, но это 1 раз из 20. И теперь это не критично даже самый большой апдейт в году (рождественский) стал проходить без запинок.

3D-отдел вообще работает через чат в Slack отправляют все изменения, рефы и остальное. Все это прямо там же обсуждается текстом, иногда голосом на созвонах. Для программистов такой формат, конечно, не подойдет. Если в коде это вызывает хаос и конкуренцию, то в арте коллективный творческий процесс. Все друг под друга калибруются, поэтому многие критичные вещи фиксятся еще до первого плейтеста.

В целом, при таком формате стало меньше бюрократии и больше творчества. Поэтому 70-80% контента доходит до плейтеста уже в отличном качестве.

Когда все фидбеки учтены, а баги пофикшены проводится итоговый плейтест перед релизом. Если все хорошо, контент готов отправиться в сторы.

8. Релиз и сбор статистики

Работа над одной пушкой от идеи до финала занимает около месяца. Это не человекочасы, а длина полного цикла пайплайна. Тайминги такие:

  • 2 недели концептирование и моделирование;

  • 1 неделя фидбек и правки;

  • 1 неделя финальные тесты и полировка.

Готовый контент отправляется в ветку Develop, отмечается в таблице и ждет своего часа. Затем происходит релиз и сразу начинается сбор метрик: что лучше продается, что чаще берут в матчах, следим за киллрейтами и собираем различную статистику.

Если есть проблема с параметрами оружия, то фиксим ее прямо на сервере, но если что-то глобальное с моделью или эффектами выпускаем хотфикс.

За этим в том числе следит отдел комьюнити. Собирает фидбеки, работает с аудиторией в отзывах, комментариях, комнатах в Discord. Обязательно нужно учитывать мнение аудитории и работать с лояльностью.

Еще могут возникать неучтенные сочетания механик, сомнительный образ или случайная отсылка на неприемлемое содержание. Но последнее больше актуально в специфических регионах вроде Китая или арабских стран. Это тоже оперативно исправляется.

9. Ревью-контента

В игре сейчас более тысячи единиц контента: оружие, карты, питомцы, броня и многое другое. Все это нужно сбалансировать между собой, но каждый раз с выходом нового контента подгонять параметры вручную просто нереально. Мы стали искать комплексное решение, что-то автоматизированное и построенное на алгоритмах. И нашли те же гугл-таблицы.

Сейчас постоянно идет фоновая работа: собирается киллрейт всех пушек, и система с помощью формул в гугл-таблицах автоматически показывает отклонения, которые нужно исправить. Расчетные формулы там настолько сложные, что не поместятся на экран монитора, но об этом поговорим уже в другом материале.

Вместо заключения

Пайплайн в разы увеличил скорость разработки. Мы полностью избавились от ситуаций, когда несколько человек заняты одной и той же задачей или переделывают что-то друг за другом. Проблемы с синком решились одной, казалось бы, простой гугл-таблицей теперь даже десятки видов контента можно делать параллельно без ошибок, овертаймов и недопониманий.

Большинство технических проблем решается еще до их появления. Но если какой-нибудь критический баг все же проскользнет его можно спокойно пофиксить даже за несколько дней до релиза и не нарушать остальные процессы.

Мы также регулярно пробуем разные методы правильной оценки и прогнозирования успешности сезонов. Дошло до того, что в поиске идеала мы уже несколько раз меняли аналитические системы. А прямо сейчас с командой аналитиков и серверных программистов пишем новую консоль с понятными и наглядными метриками для сравнения тем и сезонов, чтобы готовить контент, опираясь не на интуицию, а на цифры об этом расскажем в отдельной статье.

Подробнее..

10 Kubernetes-инструментов из разряда важно, шпаргалка по созданию Kubernetes-операторов на Java и многое другое

10.09.2020 12:19:55 | Автор: admin


Прокачивайте скилы, читайте, смотрите, думайте, применяйте на практике! Станьте частью DevNation!

Начни новое:



Качай (это легально):



Почитать на досуге:



Мероприятия:



По-русски:


Мы продолжаем серию пятничных вебинаров про нативный опыт использования Red Hat OpenShift Container Platform и Kubernetes. Регистрируйтесь и приходите:

Император Оператор: Операторы в OpenShift и Kubernetes

Упс, вебинар прошел, но есть запись.

Подробнее..

Jenkins Pipeline. Что это и как использовать в тестировании

08.02.2021 16:12:50 | Автор: admin

Меня зовут Александр Михайлов, я работаю в команде интеграционного тестирования компании ЮMoney.

Наша команда занимается приемочным тестированием. Оно включает в себя прогон и разбор автотестов на критичные бизнес-процессы в тестовой среде, приближенной по конфигурации к продакшену. Еще мы пишем фреймворк, заглушки, сервисы для тестирования в целом, создаем экосистему для автоматизации тестирования и обучаем ручных тестировщиков автоматизации.

Надеюсь, что эта статья будет интересна как новичкам, так и тем, кто съел собаку в автоматизации тестирования. Мы рассмотрим базовый синтаксис Jenkins Pipeline, разберемся, как создать джобу на основе пайплайна, а также я расскажу про опыт внедрения неочевидной функциональности в CI запуска и дожатия автотестов по условию.

Запуск автотестов на Jenkins инструкция

Не новость, что автотесты эффективнее всего проводить после каждого изменения системы. Запускать их можно локально, но мы рекомендуем делать это только при отладке автотестов. Больший профит автотесты принесут при запуске на CI. В качестве CI-сервера у нас в компании используется Jenkins, в качестве тестового фреймворка JUnit, а для отчетов Allure Report.

Чтобы запускать тесты на Jenkins, нужно создать и сконфигурировать джобу.

Для этого достаточно выполнить несколько несложных шагов.

1) Нажать Создать, выбрать задачу со свободной конфигурацией и назвать ее, например, TestJob.

Естественно, для этого у вас должны быть права в Jenkins. Если их нет, нужно обратиться к администратору Jenkins.

2) Указать репозиторий, откуда будет выкачиваться код проекта: URL, credentials и branch, с которого все будет собираться.

3) Добавить нужные параметры, в этом примере количество потоков (threadsCount) и список тестов для запуска (testList).

Значение *Test для JUnit означает Запустить все тесты.

4) Добавить команду для запуска тестов.

Наш вариант запускается на Gradle: мы указываем таску теста и передаем параметры в тесты.

./gradlew test -PthreadsCount=$threadsCount -PtestList=$testList

Можно выполнить шаг сборки Выполнить команду shell, либо через Gradle Plugin использовать шаг Invoke Gradle Script.

5) Нужно добавить Allure-report (должен быть установлен https://plugins.jenkins.io/allure-jenkins-plugin/) в Послесборочные операции, указав путь к артефактам Allure после прогона (по умолчанию allure-result).

Создав джобу и прогнав с ее помощью тестов, мы можем получить такой результат.

На скрине реальный прогон. По таймлайну видно, как тесты разделяются по потокам и где были падения.

Несложно заметить, что тесты у нас падают.

Почему падают тесты

Падения могут случаться по разным причинам. В нашем случае на это влияют:

  • ограниченные ресурсы тестового стенда,

  • большое число микросервисов (~140); если при запуске интеграционных тестов какой-то один микросервис подтормаживает, тесты начинают валиться,

  • большое число интеграционных тестов (>3000 E2E),

  • врожденная нестабильность UI-тестов.

В итоге проблемы по этим причинам мультиплицируются, и мы получаем комбинаторный взрыв, проявляющийся ошибками в прогонах тестов. Что делать? Дожимать.

Что такое дожим

Дожим это перезапуск автотестов в рамках их одного прогона. При успешном прохождении автотеста можно считать его пройденным, не учитывая предыдущие падения в прогоне.

Дожимать? Опасно же!

Возразите вы и будете полностью правы. Безусловно, дожим автотестов может спровоцировать пропуск дефекта на продакшн. Баг может быть плавающим при повторном запуске успешно просочиться и попасть на продакшн.

Но мы проанализировали статистику падений автотестов за длительный период и увидели, что большинство падений было связано с нестабильной тестовой средой, тестовыми данными. Поэтому мы взяли на себя риск и стали дожимать тесты. Прошло уже больше года, и не было ни одного дефекта, пропущенного на продакшн по этой причине. Зато стабильность тестов повысилась ощутимо.

Как решать задачу с дожимами

Мы пробовали разные решения: использовали модификацию поведения JUnit 4, JUnit 5, писали обертки на Kotlin. И, к сожалению, каждый раз реализация завязывалась на фичах языка или фреймворка.

Если процесс запускался с помощью JUnit 4 или JUnit 5, возможность перезапустить тесты была только сразу при падении. Тест упал, перезапустили его несколько раз подряд и если сбоил какой-то микросервис из-за нагрузки, либо настройки тестовой среды были некорректные, то тест все три раза падал.

Это сильно удлиняло прогон, особенно когда проблема была в настройке тестовой среды, что приводило к провалу множества тестов.

Мы взглянули на проблему шире, решили убрать зависимость от тестового фреймворка или языка и реализовали перезапуск на более высоком уровне на уровне CI. И сделали это с помощью Jenkins Pipeline.

Для этого подходил следующий алгоритм: получаем список упавших тестов и проверяем условия перезапуска. Если упавших тестов нет, завершаем прогон. Если есть, решаем, запускать их повторно или нет. Например, можно реализовать логику, чтобы не перезапускались тесты, если их упало больше какого-то критического числа. Или перед запуском можно сделать проверку доступности тестируемого сервиса.

Что такое Jenkins Pipeline

Jenkins Pipeline набор плагинов, позволяющий определить жизненный цикл сборки и доставки приложения как код. Он представляет собой Groovy-скрипт с использованием Jenkins Pipeline DSL и хранится стандартно в системе контроля версий.

Существует два способа описания пайплайнов скриптовый и декларативный.

1. Scripted:

node {stage('Example') {try {sh 'exit 1'}catch (exc) { throw exc}}}

2. Declarative

pipeline {agent anystages {stage("Stage name") {steps {}}}}

Они оба имеют структуру, но в скриптовом она вольная достаточно указать, на каком слейве запускаться (node), и стадию сборки (stage), а также написать Groovy-код для запуска атомарных степов.

Декларативный пайплайн определен более жестко, и, соответственно, его структура читается лучше.

Рассмотрим подробнее декларативный пайплайн.

  1. В структуре должна быть определена директива pipeline.

  2. Также нужно определить, на каком агенте (agent) будет запущена сборка.

  3. Дальше идет определение stages, которые будут содержаться в пайплайне, и обязательно должен быть конкретный стейдж с названием stage(name). Если имени нет, тест упадет в runtime с ошибкой Добавьте имя стейджа.

  4. Обязательно должна быть директива steps, в которой уже содержатся атомарные шаги сборки. Например, вы можете вывести в консоль Hello.

pipeline { // определение декларативного pipelineagent any // определяет, на каком агенте будет запущена сборкаstages { // содержит стейджи сборкиstage("Stage name") { // отдельный стейдж сборкиsteps { // набор шагов в рамках стейджаecho "Hello work" // один из шагов сборки}}}}

Мне нравится декларативный вид пайплайна тем, что он позволяет определить действия после каждого стейджа или, например, после всей сборки. Я рекомендую использовать его при описании пайплайнов на верхнем уровне.

pipeline {stages {stage("Post stage") {post { // определяет действия по завершении стейджаsuccess { // триггером исполнения секции является состояние сборки archiveArtifacts artifacts: '**/target/*'}}}}post { // после всей сборкиcleanup {cleanWs()}}}

Если сборка и стейдж завершились успешно, можно сохранить артефакты или почистить workspace после сборки. Если же при таких условиях использовался бы скриптовый пайплайн, пришлось бы за этим флоу следить самостоятельно, добавляя обработку исключений, условия и т.д.

При написании своего первого пайплайна я, естественно, использовал официальный мануал там подробное описание синтаксиса и степов.

Сначала я даже растерялся и не знал, с чего начать настолько там много информации. Если вы первый раз сталкиваетесь с написанием пайплайна, начать лучше со знакомства с генераторами фрагментов пайплайна из UI-интерфейса.

Если к URL вашего веб-интерфейса Jenkins добавить ендпойнт /pipelines-syntax, откроется страница, в которой есть ссылки на документацию и два сниппет-генератора, позволяющие генерировать пайплайн даже без знания его синтаксиса:

  • Declarative sections generator

  • Snippet Generator

Генераторы фрагментов помощники в мире Jenkins

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

  • Declarative sections generator (JENKINS-URL/directive-generator) генератор фрагментов для декларативного описания пайплайна.

Для добавления стадии нужно написать ее имя и указать, что будет внутри (steps). После нажатия кнопки Сгенерировать будет выведен код, который можно добавлять в пайплайн.

stage(start tests){steps{ //One or more steps needs to be included within the steps block}}

Также нам нужны шаги, в которых будут выполняться различные действия например, запуск джобы Jenkins с тестами.

  • В Sample Step выбрать build: Build a job.

  • (Дальше функционал подсказывает) необходимо определить параметры, которые будут переданы в джобу (для примера задано branch, project).

  • Нажать кнопку Generate в результате сформируется готовый рабочий код.

Изменим параметры джобы на те, которые определили при ее создании.

build job QA/TestJob, parameters: [                        string(name: 'threadsCount', value: 16),                         string(name: 'testList', value: *Test),                        string(name: 'runId', value: runId)]

где threadsCount - кол-во потоков для распараллеливания тестов, testList - список тестов для запуска, runId - идентификатор прогона тестов. Для чего нужны эти параметры, расскажу далее.

Snippet Generator удобен тем, что подсвечивает шаги в зависимости от установленных плагинов. Если у вас есть, например, Allure-report, то плагин сразу покажет наличие расширения и поможет сгенерировать код, который будет работать.

Вставим сгенерированный код степа в пайплайн на следующем шаге.

Запуск тестов с помощью Pipeline инструкция

Итак, давайте с помощью Declarative sections generator создадим пайплайн. В нем нужно указать директивы: pipeline, agent (агент, на котором будет запускаться пайплайн), а также stages и steps (вставка ранее сгенерированного кода).

Так получится пайплайн, который запустит джобу, созданную на предыдущем шаге через UI.

pipeline {agent {label any}stages {stage("start test") {steps{build job: '/QA/TestJob',parameters: [string(name: 'threadsCount', value: threadsCount),string(name: 'runId', value: runId),string(name: 'testList', value: testList)]}}} }

Напомню, что в параметры для запуска тестов мы передавали количество потоков и список тестов. Теперь к этому добавляем параметр runId (идентификатор прогона тестов) он понадобится позднее для перезапуска конкретного сьюта тестов.

Чтобы запустить пайплайн, нужно создать проект.

  1. New Item -> Pipeline.

Для этого нужно нажать на кнопку Создать проект и выбрать не джобу со свободной конфигурацией, а джобу Pipeline осталось указать ей имя.

  1. Добавить параметры runId, threadsCount, testList.

  1. Склонировать из Git.

Пайплайн можно описать непосредственно как код и вставить в поле, но для версионирования нужно затягивать пайплайн из Git. Обязательно указываем путь до скрипта с пайплайном.

Готово, джобу можно запускать.

Хотим добавить немного дожатий

На этом этапе у нас уже готова джоба для запуска проекта с автотестами. Но хочу напомнить, что наша задача не просто запускать тесты, а добавить им стабильности, исключив падения из-за внешних факторов. Для достижения этого результата было принято решение дожать автотесты.

Для реализации нужно:

  1. вынести шаг запуска тестов в библиотечную функцию (shared steps),

  2. получить упавшие тесты из прогона,

  3. добавить условия перезапуска.

Теперь немного подробнее про каждый из этих шагов.

Многократное использование шагов Shared Steps

В процессе написания пайплайнов я столкнулся с проблемой постоянного дублирования кода (часто используемых степов). Этого хотелось избежать.

Решение нашлось не сразу. Оказывается, для многократного использования кода в Jenkins есть встроенный механизм shared libraries, который позволяет описать методы один раз и затем применять их во всех пайплайнах.

Существуют два варианта подключения этой библиотеки.

  1. Написанный проект/код подключить через UI Jenkins. Для этого требуются отдельные права на добавление shared libraries или привлечение девопс-специалистов (что не всегда удобно).

  2. Хранить код в отдельном проекте или в проекте с пайплайнами. При использовании этот код подключается как динамическая библиотека и выкачивается каждый раз при запуске пайплайна.

Мы используем второй вариант размещаем shared steps в проекте с пайплайнами.

Для этого в проекте нужно:

  • создать папку var,

  • в ней создать файл с названием метода, который планируется запускать например, gradlew.groovy,

  • стандартно определить имя метода (должен называться call), то есть написать def call и определить входящие параметры,

  • в теле метода можно написать произвольный Groovy-код и/или Pipeline-степы.

Pipeline script:

//Подключение библиотеки//https://www.jenkins.io/doc/book/pipeline/shared-libraries/ - описание с картинкамиlibrary identifier: 'pipeline-shared-lib'  pipeline {stages {stage("Build") {steps {gradlew(tasks: ["build"]) // вызов метода из библиотеки}}}}

var/gradlew.groovy

def call(Map<String, List<String>> parameters) {  // стандратное имя для глобального методаdef tasks = parameters["tasks"]def args = parameters["args"] ?: []sh "./gradlew ${args.join(' ')}     ${tasks.join(' ')}"    // произвольный groovy код + pipeline-методы}

Вынесение запуска тестов в shared steps в /var

  1. Выносим startTests.groovy в /var.

Во-первых, нужно вынести запуск тестов в отдельный метод. Выглядит это так создаем файл, называем метод def call, берем кусок кода, который был в пайплайне, и выносим его в этот step.

def call(Map<String, String> params) {    def threadsCount = params["threadsCount"] ?: "3"    def testList = params["testList"] ?: "*Test"    stage("start test job") {      runTest = build job: '/QA/TestJob',                  parameters: [                         string(name: 'threadsCount', value: threadsCount),                         string(name: 'runId', value: runId),                         string(name: 'testList', value: testList)],                         propagate: false   }}

Для передачи параметров используется Map<String, String>. Почему не передавать каждый параметр отдельно? Это не очень удобно, т.к. в Groovy параметры не обозначены по названиям. При использовании Map синтаксис позволяет указать key:value через двоеточие. В коде (в месте вызова метода) это отображается наглядно.

Структура проекта будет выглядеть так.

  1. Подключение shared steps как внешней библиотеки.

Дальше нужно добавить вынесенные шаги в пайплайн. При динамической подгрузке библиотек (во время запуска пайплайна) эти шаги выкачиваются из репозитория и подключаются на лету.

Сделать это можно с помощью сниппет-генератора выбираем степ library и указываем ветку, в которую все будет собираться и репозиторий. Дальше нажимаем кнопку Сгенерировать и вставляем получившийся пайплайн.

library changelog: false,    identifier: 'shared-lib@master',    retriever: modernSCM([   $class    : 'GitSCMSource',   remote   : 'ssh://git@bitbucket.ru/qa/jenkins-groovy-scripts.git'])

Теперь после подключения shared steps вместо шага запуска тестов build нужно вставить startTest. Не забудьте, что имя метода должно совпадать с именем файла.

Теперь наш пайплайн выглядит так.

//Динамическое подключение библиотекиlibrary changelog: false,    identifier: 'shared-lib@master',    retriever: modernSCM([   $class   : 'GitSCMSource',  remote   : 'ssh://git@bitbucket.ru/qa/jenkins-groovy-scripts.git'])pipeline {   agent {  label any  }  stages { stage("start test") {    steps{   startTests(runId: runId ) //Вызов метода из библиотеки  }    }  }}

Первый шаг реализован, теперь можно многократно запускать тесты в разных местах. Переходим к 2 шагу.

Получение упавших тестов из прогона

Теперь нам нужны упавшие тесты. Каким образом их извлечь?

  • Установить в Jenkins плагин JUnit Test Result Report и использовать его API.

  • Взять результаты прогона JUnit (обычно в формате XML), распарсить и извлечь нужные данные.

  • Запросить список упавших тестов из нужного места.

В нашем случае таким местом является собственный написанный сервис Reporter. Во время прогона тестов результат по каждому из них отправляется именно туда. В конце прогона делаем http-запрос и получаем упавшие тесты.

http://reporter:8080/failedTests/$runId

Добавление условий перезапуска

На этом шаге следует добавить getFailedTests.groovy в /var. Представим, что у вас есть такой сервис Reporter. Нужно назвать файл getFailedTests, сделать запрос httpRequest в этот сервис и распарсить его.

def call(String runId) {    def response = httpRequest httpMode: 'GET',     url: "http://reporter:8080/failedTests/$runId"     def json = new JsonSlurper().parseText(response.content)        return json.data}

Отлично, второй шаг выполнен. Осталось добавить ту самую изюминку условия перезапуска. Но сначала посмотрим, что получается.

Есть запуск тестов и получение результатов прогона. Теперь нужно добавить те самые условия, которые будут говорить: Если все хорошо, завершай прогон. Если плохо, давай перезапускать.

Условия перезапуска

Какие условия для перезапуска можно реализовать?

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

1) Если нет упавших тестов, прогон завершается.

if (countFailedTests == 0) {echo FINISHED   }

2) Как я уже писал выше, на тестовой среде ресурсы ограничены, и бывает такое, что ТС захлебывается в большом количестве параллельных тестов. Чтобы на дожатии избежать падений тестов по этой причине, понижаем число потоков на повторном запуске. Именно для этого при создании джобы и в самом пайплайне мы добавили параметр threadsCount.

Если и после уменьшения потоков тесты не дожимаются (количество упавших тестов на предыдущем прогоне равно числу упавших тестов на текущем), тогда считаем, что дело не во влиянии ТС на тесты. Останавливаем прогон для анализа причин падений и тем самым предотвращаем последующий холостой запуск.

if (countFailedTests == previousCountFailedTests) { echo TERMINATED - no one new passed test after retry}

3) Третья и самая простая проверка состоит в том, что если падает большое количество тестов, то дожимать долго. Скорее всего, причина падений какая-то глобальная, и ее нужно изучать.

Для себя мы определили: если тестов > 40, дожимать не автоматически не будем, потому что 40 наших E2E могут проходить порядка 15 минут.

if (countFailedTests > FAILEDTESTSTRESHOLD) {   echo TERMINATED - too much failed tests   }
Получился метод:

https://github.com/useriq/retry-flaky-tests/blob/master/jenkins-pipeline-retry/var/testsWithRerun.groovy

def call(Map<String, String> params) {    assert params["runId"]    def threadsCount = params["threadsCount"] ?: "8"    def testList = params["testList"] ?: "*Test"    def runId = params["runId"]    int FAILED_TESTS_TRESHOLD = 40    def countFailedTests = 0    def failedTests    int run = 1    boolean isFinished = false    int threads = threadsCount as int    while (run <= Integer.valueOf(runCount) && !isFinished) {        if (run == 1) {            startTests()        } else {            if (countFailedTests > 0) {                threads = reduceThreads(threads)                testList = failedTests.toString().minus('[').minus(']').minus(' ')                startTests()            }        }        stage("check ${run}_run result ") {            failedTests = getFailedTests(runId)            def previousCountFailedTests = countFailedTests            countFailedTests = failedTests.size()            if (countFailedTests == 0) {                echo "FINISHED"                isFinished = true            }            if (countFailedTests > FAILED_TESTS_TRESHOLD) {                echo "TERMINATED - too much failed tests > ${FAILED_TESTS_TRESHOLD}"                isFinished = true            }            if (countFailedTests == previousCountFailedTests) {                echo "TERMINATED - no one new passed test after retry"                isFinished = true            }        }        run += 1    }}

Последние два условия так называемые fail fast. Они позволяют при глобальных проблемах на тестовом стенде не делать прогоны, которые не приведут к дожиму тестов, но будут занимать ресурсы тестового стенда.

Итоговый pipeline

Итак, все 3 шага реализованы итоговый пайплайн выглядит так.

library changelog: false,       identifier: 'shared-lib@master',       retriever: modernSCM([               $class       : 'GitSCMSource',               remote       : 'ssh://git@bitbucket.ru/qa/jenkins-groovy-scripts.git']) assert runId != nullpipeline {   agent {       label any   }   stages {       stage("start test") {           steps {             testsWithRerun(runId: runId)           }       }   }}

Визуализация с Blue Ocean

Как все это выглядит при прогоне в Jenkins? У нас, к примеру, для визуализации в Jenkins установлен плагин Blue Ocean.

На картинке ниже можно увидеть, что:

  1. запустился метод testwith_rerun,

  2. прошел первый запуск,

  3. прошла проверка упавших тестов,

  4. запустился второй прогон,

  5. после успешной проверки джоба завершилась.

Вот так выглядит визуализация нашего настоящего прогона.

В реальном примере две ветки: мы параллельно запускаем два проекта с тестами (на Java и Kotlin). Можно заметить, что тесты одного проекта прошли с первого раза, а вот тесты другого пришлось дожимать еще раз. Таким образом визуализация помогает найти этап, на котором падают тесты.

А так выглядит реальный timeline приемки релиза.

После первого прогона отправили дожимать упавшие тесты. Во второй раз упавших тестов намного меньше, дожимаем в третий раз и вуаля, успешный build.

Задача решена.

Итог

Мы перенесли логику перезапусков упавших тестов из тестового проекта на уровень выше на CI. Таким образом сделали механизм перезапуска универсальным, более гибким и независимым от стека, на котором написаны автотесты.

Раньше наши тесты дожимались безусловно, по несколько раз, с неизменным количеством параллельных потоков. При перегрузке тестового стенда, некорректных настройках тестового окружения либо каких-то других проблемах красные тесты перезапускались фиксированное число раз без шансов дожаться. В худшем случае прогоны могли длиться часами. Добавив условия fail fast, мы сократили это время.

При падении тестов инженер, ответственный за приемку, в некоторых ситуациях вручную стартовал джобу перезапуска, выполняя прогон с меньшим числом потоков. На это тоже уходило время. Добавив в условия пайплайна уменьшение числа потоков на перезапуске, мы сократили и это время.

Какой профит мы получили:

  • уменьшили time-to-market тестируемых изменений,

  • сократили длительность аренды тестового стенда под приемочное тестирование,

  • увеличили пропускную способность очереди приемочного тестирования,

  • не завязаны на тестовый фреймворк (под капотом может быть что угодно дожатия будут работать),

  • поделились знаниями об использовании Jenkins Pipeline.

Примеры кода выложены на GitHub. Если будут вопросы, задавайте обязательно отвечу.

Подробнее..

Идеальный пайплайн в вакууме

03.06.2021 22:22:36 | Автор: admin
Даже не зовите меня, если ваш pipeline не похож на это.Даже не зовите меня, если ваш pipeline не похож на это.

На собеседованиях на позицию, предполагающую понимание DevOps, я люблю задавать кандидатам такой вопрос (а иногда его еще задают и мне):

Каким, по вашему мнению, должен быть идеальный пайплайн от коммита до продашкена?/Опишите идеальный CI/CD / etc

Сегодня я хочу рассказать про своё видение идеального пайплайна. Материал ориентирован на людей, имеющих опыт в построении CI/CD или стремящихся его получить.

Почему это важно?

  1. Вопрос об идеальном пайплайне хорош тем, что он не содержит точного ответа.

  2. Кандидат начинает рассуждать, а в крутых специалистах ценится именно умение думать.

  3. Когда в вопрос добавляется такое абсолютное прилагательное, как "идеальный", то мы сразу развязываем кандидатам руки в просторе для творчества и фантазий. У соискателей появляется возможность показать, какие улучшения они видят (или не видят) в текущей работе, и что хотели бы добавить сами. Также мы можем узнать, есть ли у нашего предполагаемого будущего коллеги мотивация к улучшениям процессов, ведь концепция "работает не трогай" не про динамичный мир DevOps.

  4. Организационная проверка. Позволяет узнать, насколько широка картина мира у соискателя. Условно: от создания задачи в Jira до настроек ноды в production. Сюда же можно добавить понимание стратегий gitflow, gitlabFlow, githubFlow.

Итак, прежде чем перейти к построению какого-либо процесса CI, необходимо определиться, а какие шаги нам доступны?

Что можно делать в CI?

  • сканить код;

  • билдить код;

  • тестить код;

  • деплоить приложение;

  • тестить приложение;

  • делать Merge;

  • просить других людей подтверждать MR через code review.

Рассмотрим подробнее каждый пункт.

Code scanning

На этой стадии основная мысль никому нельзя верить.

Даже если Вася Senior/Lead Backend Developer. Несмотря на то, что Вася хороший человек/друг/товарищ и кум. Человеческий фактор, это все еще человеческий фактор.

Необходимо просканировать код на:

  • соотвествие общему гайдлайну;

  • уязвимости;

  • качество.

Мне нужны твои уязвимости, сапоги и мотоциклМне нужны твои уязвимости, сапоги и мотоцикл

Задачи на этой стадии следует выполнять параллельно.

И триггерить только если меняются исходные файлы, или только если было событие git push.

Пример для gitlab-ci

stages:  - code-scanning.code-scanning: only: [pushes] stage: code-scanning 

Linters

Линтеры это прекрасная вещь! Про них уже написано много статей. Подробнее можно почитать в материале "Холиварный рассказ про линтеры".

Самая важная задача линтеров приводить код к единообразию.

После внедрения этой штучки разработчики начнут вас любить. Потому что они наконец-то начнут понимать друг друга. Или ненавидеть, решив, что вы вставляете им палки в колеса линтеры в CI. Это уже зависит от ваших soft skills, культуры и обмена знаниями.

Инструменты

Инструмент

Особенности

eslint

JavaScript

pylint

Python

golint

Golang

hadolint

Dockerfile

kubeval

Kubernetes manifest

shellcheck

Bash

gixy

nginx config

etc

Code Quality

code quality этими инструментами могут быть как продвинутые линтеры, так и совмещающие в себе всякие ML-модели на поиск слабых мест в коде: утечек памяти, небезопасных методов, уязвимостей зависимостей и т.д, перетягивая на себя еще code security компетенции.

Инструменты

Инструмент

Особенности

Price

SonarQube

Поиск ошибок и слабых мест в коде

От 120

CodeQL

Github native, поиск CVE уязвимостей

OpenSource free

etc

Code Security

Но существуют также и отдельные инструменты, заточенные только для code security. Они призваны:

  1. Бороться с утечкой паролей/ключей/сертификатов.

  2. Cканировать на известные уязвимости.

Неважно, насколько большая компания, люди в ней работают одинаковые. Если разработчик "ходит" в production через сертификат, то для своего удобства разработчик добавит его в git. Поэтому придется потратить время, чтобы объяснить, что сертификат должен храниться в vault, а не в git

Инструменты

Инструмент

Особенности

Price

gitleaks

Используется в Gitlab Security, может сканить промежуток от коммита "А" до коммита "Б".

Free

shhgit

Запустили недавно Enterpise Edition.

От $336

etc

Сканер уязвимостей необходимо запускать регулярно, так как новые уязвимости имеют свойство со временем ВНЕЗАПНО обнаруживаться.

Да-да, прямо как Испанская Инквизиция!Да-да, прямо как Испанская Инквизиция!

Code Coverage

Ну и конечно, после тестирования, нужно узнать code coverage.

Процент исходного кода программы, который был выполнен в процессе тестирования.

Инструменты

Инструмент

Особенности

Price

go cover

Для Golang. Уже встроен в Golang.

Free

cobertura

Работает на основе jcoverage. Java мир

Free

codecov

Старая добрая классика

Free до 5 пользователей

etc

Unit test

Модульные тесты имеют тенденцию перетекать в инструменты code quality, которые умеют в юнит тесты.

Инструменты

Инструмент

Особенности

phpunit

PHP (My mom says I am special)

junit

Java (многие инстурменты поддерживают вывод в формате junit)

etc

Build

Этап для сборки artifacts/packages/images и т.д. Здесь уже можно задуматься о том, каким будет стратегия версионирования всего приложения.

За модель версионирования вы можете выбрать:

Во времена контейнеризации, в первую очередь интересуют образы для контейнеров и способы их версионирования.

Инструменты для сборки образов

Инструмент

Особенности

docker build

Почти все знают только это.

buildx / buildkit

Проект Moby предоставил свою реализацию. Поставляется вместе с докером, включается опцией DOCKER_BUILDKIT=1.

kaniko

Инструмент от Google, позволяет собирать в юзерспейсе, то есть без докер-демона.

werf

Разработка коллег из Флант'а. Внутри stapel. All-in-one: умеет не только билдить, но и деплоить.

buildah

Open Container Initiative, Podman.

etc

Итак, сборка прошла успешно идем дальше.

Scan package

Пакет/образ собрали. Теперь нужно просканировать его на уязвимости. Современные registry уже содержат инструментарий для этого.

Инструменты

Инструмент

Особенности

Цена

harbor

Docker Registry, ChartMuseum, Robot-users.

Free

nexus

Есть все в том числе и Docker.

Free и pro

artifactory

Комбайн, чего в нем только нет.

Free и pro

etc

Deploy

Стадия для развертывания приложения в различных окружениях.

Деплоим контейнер в прод, как можем.Деплоим контейнер в прод, как можем.

Не все окружения хорошо сочетаются со стратегиями развертывания.

  • rolling классика;

  • recreate все что угодно, но не production;

  • blue/green в 90% процентов случаев этот способ применим только к production окружениям;

  • canary в 99% процентов случаев этот способ применим только к production окружениям.

Stateful

Нужно еще помнить, что даже имея одинаковый код в stage и production, production может развалиться именно из-за того, что stateful у них разный. Миграции могут отлично пройти на пустой базе, но появившись на проде, сломать зеленые кружочки/галочки в пайплайне. Поэтому для stage/pre-production следует предоставлять обезличенный бэкап основной базы.

И не забудьте придумать способ откатывания ваших релизов на последний/конкретный релиз.

Инструменты

Инструмент

Особенности

helmwave

Docker-compose для helm. Наша разработка.

helm

Собираем ямлики в одном месте.

argoCD

"Клуб любителей пощекотать GitOps".

werf.io

Было выше.

kubectl / kustomize

Для тех, кто любит сам придумывать шаблонизацию.

etc

На правах рекламы скажу что helmwav'у очень не хватает ваших звезд на GitHub. Первая публикация про helmwave.

Integration testing

Приложение задеплоили. Оно где-то живет в отдельном контуре.Наступает этап интеграционного тестирования. Тестирование может быть как ручным, так и автоматизированным. Автоматизированные тесты можно встроить в пайплайн.

Инструменты

Инструмент

Особенности

Selenium

Можно запустить в кубере.

Selenoid

Беды с образами. Требует Docker-in-Docker.

etc

Performance testing (load/stress testing)

Данный вид тестирования имеет смысл проводить на stage/pre-production окружениях. С тем условием, что ресурсные мощности на нем такие же, как в production.

Инструменты, чтобы дать нагрузку

Инструмент

Особенности

wrk

Отличный молоток. Но не пытайтесь прибить им все подряд.

k6.io

Cтильно-модно-JavaScript! Используется в AutoDevOps.

Artillery.io

Снова JS. Сравнение с k6

jmeter

OldSchool.

yandex-tank

Перестаньте дудосить конурентов.

etc

Инструменты, чтобы оценить работу сервиса

Инструмент

Особенности

sitespeed.io

Внутри: coach, browserTime, compare, PageXray.

Lighthouse

Тулза от Google. Красиво, можешь показать это своему менеджеру. Он будет в восторге. Жаль, только собаки не пляшут.

etc

Code Review / Approved

Одним из важнейших этапов являются Merge Request. Именно в них могут производиться отдельные действия в pipeline перед слиянием, а также назначаться группы лиц, требующих одобрения перед cлиянием.

Список команд/ролей:

  • QA;

  • Security;

  • Tech leads;

  • Release managers;

  • Maintainers;

  • DevOps;

  • etc.

Очевидно, что созывать весь консилиум перед каждым MR не нужно, каждая команда должна появится в свой определённый момент MR:

  • вызывать безопасников имеет смысл только перед сливанием в production;

  • QA перед release ветками;

  • DevOps'ов беспокоить, только если затрагиваются их компетенции: изменения в helm-charts / pipeline / конфигурации сервера / etc.

Developing flow

Чаще всего каждая компания, а то и каждый проект в компании, решает изобрести свой велосипед-флоу. Что после нескольких итераций приходит к чему-то, что может напоминать gitflow, gitlabFlow, githubFlow или все сразу.

Это и не хорошо, и не плохо это специфика проекта. Есть мнения, что gitflow не торт. GithubFlow для относительно маленьких команд. А про gitlabFlow мне нечего добавить, но есть наблюдение, что его не очень любят продакты - за то, что нельзя отслеживать feature-ветки.

Если вкратце, то:

  • Gitflow: feature -> develop -> release-vX.X.X -> master (aka main) -> tag;

  • GitHubFlow: branch -> master (aka main);

  • GitLabFlow: environmental branches.

TL;DR

Общий концепт

_

Feature-ветка

Pre-Production -> Production

P.S.

Если я где-то опечатался, упустил важную деталь или, по вашему мнению, пайплайн недостаточно идеальный, напишите об этом мне сделаю update.

Разработчик создал ветку и запушил в нее код. Что дальше?

Оставляйте варианты ваших сценариев в комментариях.

Подробнее..

Перевод Семь паттернов пайплайнов непрерывной поставки

23.11.2020 20:22:46 | Автор: admin

Перевод статьи подготовлен в преддверии старта курса "DevOps практики и инструменты".

Прямо сейчас у вас есть шанс успеть на курс по специальной цене. Узнать подробности.


В настоящее время гибкость бизнеса часто основана на гибкости кода. Возможность быстрых и безопасных релизов по требованию для современных цифровых продуктов и услуг является реальным конкурентным преимуществом.

С 2004 года мы разрабатываем, собираем и развертываем пайплайны кода для автоматизации приложений и инфраструктур. В этой статье мы делимся семью паттернами, которые улучшают скорость, гибкость и качество, при одновременном повышении автономности, прозрачности и удобства обслуживания.

Непрерывная поставка

Непрерывная поставка (Continuous Delivery) это "возможность надежно и быстро поставлять изменения всех типов в руки пользователей". Если посмотреть на непрерывную поставку по матрице Agile vs Effort, то можно увидеть, что она находится прямо между непрерывной интеграцией и непрерывным развертыванием. Часто их вместе называют CI / CD.

В отчете о состоянии DevOps за 2019 год более 31 000 респондентов сообщили об эффективности своих процессов разработки и поставки. Но разница между лидерами и отстающими ошеломляет. У лидеров в 200 раз больше развертываний и в 100 раз выше скорость развертывания, а также в 2 600 раз быстрее восстановление после инцидентов и в 7 раз меньше вероятность отката релизов.

Это исследование показывает, что для лидеров скорость и стабильность не противоположны! Вы можете получить и то и другое (на самом деле вам нужно и то и другое), чтобы получить реальные конкурентные преимущества для ваших цифровых продуктов и услуг.

Пайплайны

Пайплайны (конвейеры) это основные технические артефакты непрерывной поставки. Современные пайплайны преобразуют исходный код приложения и инфраструктуры в версионированные пакеты, которые можно разворачивать в любой среде. Автоматизируя все рутинные задачи по сборке и развертыванию систем, разработчики могут сосредоточиться на реализации действительно полезных фич.

Хотя пайплайны кода существуют уже почти 20 лет CruiseControl, один из наших первых любимцев, был выпущен в 2001 году, они значительно эволюционировали за последние годы.

Основываясь на нашем опыте и опыте наших клиентов, мы выделили семь паттернов пайплайнов, которые мы наблюдали во многих современных технологических компаниях.

Паттерны пайплайнов

Паттерн 1 пайплайны как код

Логика пайплайна кодируется и хранится вместе с кодом приложения и инфраструктуры. Для выполнения пайплайнов используются контейнеры.

  • Никаких настроек через графический интерфейс! Вся логика пайплайна управляется, как и любой другой код приложения, и подчиняется тем же стратегиям ветвления и ревью.

  • Выполнение пайплайнов в контейнерах позволяет CI / CD - платформе поддерживать множество различных рабочих нагрузок, при этом каждая рабочая нагрузка может иметь свою собственную среду сборки для удовлетворения специфических требований.

  • Для образов контейнеров сред сборки используются проверенные Docker-образы.

  • Конфигурация CI runner автоматизированная, одинаковая и не требует ручной настройки. CI runner при необходимости могут масштабироваться и находиться в режиме ожидания для минимизации задержек.

  • Секреты хранятся вне пайплайнов, а их вывод маскируется, что повышает безопасность.

Паттерн 2 перенос логики в повторно используемые библиотеки

Общая логика пайплайнов выносится в повторно используемые библиотеки, на которые можно ссылаться из кода пайплайнов, а также независимо их разрабатывать и тестировать.

  • Относитесь к пайплайн-библиотекам как к любому другому программному обеспечению. У них есть собственные репозитории, пайплайны, модульные тесты и релизы.

  • По возможности для запуска внешних задач пайплайны используют специфичные для языка инструменты, такие как Make, Rake, npm, Maven и т.п. Для того чтобы упростить пайплайн и сохранить идентичность процесса локальной сборки и CI.

  • Библиотеки легко найти и у них хорошая документацию.

Паттерн 3 отдельные пайплайны для сборки и развертывания

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

  • Должна быть одна сборка и много развертываний. Сосредоточьтесь на сборке. В ее результате появляются артефакты, которые можно развернуть несколько раз.

  • Будьте независимы от окружения. При отсутствии пакетов, специфичных для окружения, и вынесения параметров в переменные окружения, одна и та же сборка может работать в любой среде.

  • Упакуйте всё вместе. Всё весь исходный код, включая код инфраструктуры, должен храниться вместе, чтобы стать версионнированным пакетом.

Паттерн 4 запуск правильного пайплайна

Коммиты в ветки, пул реквесты и слияние с основной веткой всё это может инициировать различное поведение пайплайна, оптимизированное под конкретную команду.

  • Открытие pull request'а создает эфемерную среду для тестирования.

  • Слияния с основной веткой развертываются в не-продакшн или демо-среде, содержащей последний интегрированный код.

  • Создание новых тэгов приводит к выпуску релиза.

Паттерн 5 быстрая обратная связь

Каждый коммит автоматически запускает определенный пайплайн. При этом пайплайны сборки специально оптимизированы для скорости и быстрого оповещения о возникающих проблемах.

  • Пайплайны сборки используют распараллеливание для независимых друг от друга заданий с целью повышения производительности.

  • Быстрые пайплайны сборки выполняют необходимые задания за несколько минут.

  • Каждый успешный запуск завершается созданием версии пакета и результатами статического анализа.

  • С помощью многоканальных уведомлений вы можете настроить оповещения о статусе пул реквестов на дашбордах, в чатах, по электронной почте и др.

Паттерн 6 стабильные внутренние релизы

Развертываются только версионнированные пакеты, созданные пайплайном сборки. Эти развертывания запускаются вручную или автоматически по событиям.

  • Каждая ветка кода получает полное эфемерное окружение, названное в соответствии с именем ветки, которое можно легко создать или уничтожить.

  • Любой инженер может создать или удалить эфемерную среду.

  • CI runners используют возможности cloud-native IAM с временными разрешениями, чтобы получить необходимые роли и разрешения для выполнения своей работы.

Паттерн 7 история релизов

Разверните тегированные релизы в продакшн, автоматизируйте отчетность и оставьте историю развертываний.

  • "Релизный шлюз" (release gate) в виде кода и стандартизированные процессы релиза позволяют выпускать релизы по требованию.

  • Автоматизированные релизы оставляют историю, которую можно проанализировать в дальнейшем.

  • Release gate может вызывать внешние API и использовать ответ, чтобы решить продолжать релиз или остановить его.

Проблемы

Мы все чаще встречаем эти семь паттернов пайплайнов и используем их при работе с клиентами. Но несмотря на то что они представляют собой огромный скачок вперед с точки зрения скорости и стабильности, они не лишены недостатков.

Безопасность это самая большая проблема, которую мы видим. Она связана со сложностью автоматизации процессов, которые традиционно были ориентированы на человека. Сложность пайплайнов, принятие командой, изменение в культуре и автоматизация баз данных другие серьезные проблемы, над которыми нужно работать. Но это все решаемо, и мы ежедневно работаем над этим.

Итог

Гибкость бизнеса основана на гибкости кода. Для современных цифровых продуктов и сервисов возможность быстрого и безопасного релиза по требованию является реальным конкурентным преимуществом бизнеса. Пайплайны кода и, в частности, описанные семь паттернов могут помочь вашей организации сделать гигантский шаг вперед в скорости и стабильности релизов, а ваши команды будут работать на уровне лидеров.

Посмотреть специальное предложение.

Читать ещё:

Подробнее..

Перевод Ускоряем CICD-пайплайн с помощью Kubernetes в Docker (KinD)

30.12.2020 20:10:58 | Автор: admin
В нашей новой переводной статье разбираемся с KinD на практическом примере.

Создание кластера Kubernetes со временем становится все проще. На рынке доступно несколько решений под ключ, и сейчас никто не выбирает сложный путь!



Стоит отметить, что Minikube был одним из основных кластеров, которые разработчики использовали для быстрой разработки и тестирования контейнеров. Хотя Minikube в настоящее время поддерживает многоузловой кластер на экспериментальной основе, его еще нет в общем доступе (GA).

Следовательно, это ограничивает возможности интеграции и тестирования компонентов, поэтому большинство организаций используют для этого управляемые облачные сервисы Kubernetes.
Для интеграции Kubernetes в конвейер CI/CD (непрерывная интеграция и развертывание) и выполнения тестирования необходимы следующие инструменты: Terraform, в зависимости от облачного провайдера и, конечно же, инструмент для CI/CD, например Jenkins, GitLab или GitHub.

Для крупных компаний с достаточным бюджетом это подходящие варианты, однако разработчики часто ищут что-то, что поможет им быстро начать работу. Развертывание кластера Kubernetes в облаке также занимает некоторое время (~ 10 минут), что может быть препятствием для CI, где нужно быстро получать сборки.
Kubernetes в Docker или KinD это реализация подхода Docker-in-Docker (DinD) для Kubernetes. Этот инструмент создает контейнеры, которые действуют как узлы Kubernetes, и вам необходимо только установить Docker на своем компьютере.

Он позволяет развернуть многоузловой кластер за пару минут без зависимости от других инструментов или облачных провайдеров. Благодаря этому он полезен не только для локальной разработки, но и для CI/CD.

Архитектура KinD


Kubernetes в Docker использует подход Docker-in-Docker (DinD) для запуска кластера Kubernetes. Он запускает несколько Docker контейнеров, которые функционируют как узлы Kubernetes. Контейнеры Docker монтируют том docker.sock в Docker, запущенный на вашем компьютере, чтобы обеспечить взаимодействие с базовой средой выполнения контейнера.



KinD прошел проверку на соответствие и получил сертификат CNCF. Он использует Kubeadm для начальной загрузки кластера, а также генерирует файлы конфигурации Kube для пользователя, через которого вы управляете вашим кластером, что позволяет использовать kubectl для взаимодействия с кластерами. Другие компоненты Kubernetes, такие как Helm и Istio, также прекрасно работают в кластерах KinD.

Недостаток KinD заключается в том то, что он не работает со службами LoadBalancer, поэтому вам придется использовать NodePort, чтобы пробрасывать свои службы извне.

Кроме того, DinD в настоящее время является не самым безопасным решением, поэтому используйте кластеры KinD только на локальных машинах разработки и CI/CD конвейерах. Никогда не используйте KinD в производственной среде!

Установка KinD


KinD состоит из простой утилиты командной строки, которую вы можете загрузить и переместить на свой путь. Затем вы можете взаимодействовать с KinD, используя команды kind:

sudo curl -sL https://kind.sigs.k8s.io/dl/v0.9.0/kind-linux-amd64 -o /usr/local/bin/kindsudo chmod +x /usr/local/bin//kind


Затем вы можете создать свой кластер, при помощи следующей команды:

kind create cluster --wait 10m


Эта команда создаст одноузловой кластер. Но если вы хотите определить многоузловой кластер, можно использовать файл конфигурации кластера, подобный приведенному ниже:

# three node (two workers) cluster configkind: ClusterapiVersion: kind.x-k8s.io/v1alpha4nodes:- role: control-plane- role: worker- role: worker


Затем создайте кластер с файлом конфигурации, используя следующую команду:

kind create cluster --wait 10m --config kind-config.yaml


Вы также можете создать кластеры с несколькими уровнями управления, указав несколько ролей в разделе узлов.

Так как KinD автоматически создает файл конфигурации Kube, вы можете использовать команды kubectl, как и в случае с другими кластерами.

Удалить кластер KinD также просто. Запустите следующую команду:

kind delete cluster


Приступаем к практике


Без лишних слов, давайте на практике разберемся, как CI/CD конвейер использует KinD. Мы возьмем GitHub Actions в качестве инструмента для CI/CD, поскольку он прост в использовании, не требует дополнительной инфраструктуры, а запустить его может любой, у кого есть ноутбук и подключение к интернету.

Давайте создадим простое приложение NGINX с надписью Hello World.

Выполняем следующие действия:

1. Создаем дев-версию приложения.

2. Запускаем тестирование компонентов в кластере KinD.

3. Если тест проходит успешно, мы переводим образ в релиз и отправим его в Docker Hub.

Необходимые условия


  • Аккаунт GitHub
  • Аккаунт Docker Hub


Краткое руководство


1. Разветвите этот репозиторий.

2. Зайдите в репозиторий и создайте два secret: DOCKER_USER и DOCKER_PW. Они должны содержать ваше имя пользователя в Docker Hub и пароль от аккаунта соответственно.

3. Перейдите в GitHub Actions и повторно запустите задачи. Как вариант, вы можете внести изменения в файл README.md и нажать на него чтобы запустить действие.

Длинная версия


Давайте рассмотрим файл build-pipeline.yml на GitHub Actions чтобы понять, как он работает:

name: Docker Image CIon: [push]     # Environment variables available to all jobs and steps in this workflowenv: # Or as an environment variable      docker_username: ${{ secrets.DOCKER_USER }}      docker_password: ${{ secrets.DOCKER_PW }}jobs:  build-docker-image:    runs-on: ubuntu-latest    steps:    - uses: actions/checkout@v2      with:        fetch-depth: 0    - name: Build the Docker image      run: docker build -t $docker_username/nginx:dev .    - name: Login to Docker      run: echo "$docker_password" | docker login -u "$docker_username" --password-stdin    - name: Push the docker image      run: docker push $docker_username/nginx:dev  kubernetes-component-test:    runs-on: ubuntu-latest    needs: build-docker-image    steps:    - uses: actions/checkout@v2      with:        fetch-depth: 0    - name: Run KIND Test      run: sudo sh build-test.sh $docker_username    promote-and-push-docker-image:    runs-on: ubuntu-latest    needs: kubernetes-component-test    steps:    - uses: actions/checkout@v2      with:        fetch-depth: 0    - name: Pull the Docker image      run: docker pull $docker_username/nginx:dev    - name: Tag the Docker image      run: docker tag $docker_username/nginx:dev $docker_username/nginx:release    - name: Login to Docker      run: echo "$docker_password" | docker login -u "$docker_username" --password-stdin    - name: Push the docker image      run: docker push $docker_username/nginx:release


Пайплайн сборки запускает последовательно три задания:

1. Задача build-docker-image создает Docker образ для разработки и отправляет его в Docker Hub при успешной сборке. В этой задаче вы можете запустить своё модульное тестирование.

2. Задача kubernetes-component-test настраивает кластер KinD и запускает компонентный тест для приложения.

3. Задача promote-and-push-docker-image извлекает образ для разработки, маркирует его до релизной версии и отправляет релизную версию в Docker Hub.

Давайте рассмотрим Dockerfile, чтобы понять, что он создает:

FROM nginxRUN echo 'Hello World' > /usr/share/nginx/html/index.html


Второй шаг является ключевым, он запускает скрипт build-test.sh. Сейчас давайте посмотрим на скрипт:

#! /bin/bashdocker_username=$1set -xecurl -sL https://kind.sigs.k8s.io/dl/v0.9.0/kind-linux-amd64 -o /usr/local/bin/kindchmod 755 /usr/local/bin//kindcurl -sL https://storage.googleapis.com/kubernetes-release/release/v1.17.4/bin/linux/amd64/kubectl -ochmod 755 /usr/local/bin//kubectlcurl -LO https://get.helm.sh/helm-v3.1.2-linux-amd64.tar.gztar -xzf helm-v3.1.2-linux-amd64.tar.gzmv linux-amd64/helm /usr/local/bin/rm -rf helm-v3.1.2-linux-amd64.tar.gzkind versionkubectl version --client=truehelm versionkind create cluster --wait 10m --config kind-config.yamlkubectl get nodesdocker build -t $docker_username/nginx:dev .kind load docker-image $docker_username/nginx:devkubectl apply -f nginx-deployment.yamlkubectl apply -f nginx-service.yamlNODE_IP=$(kubectl get node -o wide|tail -1|awk {'print $6'})NODE_PORT=$(kubectl get svc nginx-service -o go-template='{{range.spec.ports}}{{if .nodePort}}{{.nodesleep 60SUCCESS=$(curl $NODE_IP:$NODE_PORT)if [[ "${SUCCESS}" != "Hello World" ]];then kind -q delete clusterexit 1;else kind -q delete clusterecho "Component test succesful"fi


Что делает скрипт:

1.Скачивает и устанавливает утилиту kind, kubectl и helm на CI-сервер.
2.Создает многоузловой кластер с помощью файла kind-config.yaml.
3.Создает Docker образ для разработки с помощью docker build.
4.Загружает Docker образ в кластер KinD. Благодаря загрузке обеспечивается доступ к образу для всех узлов KinD, чтобы им не приходилось извлекать образ из Docker Hub.
5.Разворачивает контейнер в deployment и пробрасывает его через сервис NodePort NodePortservice.
6.Получает IP-адрес и порт узла и и запускает тест, чтобы проверить, возвращает ли приложение фразу Hello World.
7.Если проверка проходит успешно, удаляет кластер KinD, выводит Component test successful (успешное выполнение компонентного теста) и возвращает код успеха. Если проверка не пройдена, удаляет кластер KinD и возвращает код ошибки.

Результаты


Когда мы начинаем работу с пайплайном, GitHub Actions автоматически запускает весь пайплайн:



Это, несомненно улучшение и удобный способ для выполнения непрерывной интеграции и развертывания с использованием Docker и Kubernetes. Kubernetes в Docker не только упрощает локальную разработку, но и является отличным инструментом для CI/CD.

Спасибо, прочитали статью! Надеюсь, она вам понравилась!
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru