В этой статье я подробно расскажу о мыслительном процессе и реализации автоматизированного конвейера сборки и развертывания, который я написал, чтобы сократить время, полученное для получения отзывов для изменений кода, которые я вносил в свои кодовые базы.
Большая часть этого кода написана в Go и Bash. Это очень взломанная реализация, но было очень весело писать и использовать.
Отказ от ответственности: этот пост довольно Код тяжел, но я стараюсь выделить процесс мысли за кодом.
Вы можете найти код для всего этого поста здесь CICDEXAMER
Это быстрый гиф того, что мы собираемся построить:
Обратная связь важна
Самая важная часть для меня при написании какого -либо кода — это то, как быстро я могу получить отзыв.
Ранее в этом году я начал учиться в свободное время, и, когда я учился, я хотел рассказать код приложения (я писал свое первое веб -приложение в Go) и в конце концов разместил его на Heroku.
Так это то, что я делал в течение нескольких дней:
В общей сложности мне потребовалось ~ 10 минут, чтобы проверить новые функции в «производственной» среде. Нет точно плохого времени, когда вы работаете над чем-то для своего дневного занятия, но для Pet Project это было не так весело.
Дополнительным недостатком приведенного выше конвейера является то, что действительно трудно проверить функциональность, когда у меня нет согласованного подключения к Интернету, что происходит довольно часто. Поэтому я начал думать об альтернативах.
Я хотел иметь минимальную задержку между ctrl-s
и тестирование изменения — вроде этой диаграммы:
Компромисс по согласованности окружающей среды
Я понял, что тестирование на Heroku не было трудным требованием для моего процесса разработки. Но я действительно хотел сходства с моей «производственной» средой в моей тестовой среде.
Я начал набросать простой трубопровод CI/CD, который мог запустить на своем ноутбуке, который позволил бы мне проверить мое приложение, работающее в Docker на Похоже облачная инфраструктура.
Кроме того, я как бы знал, что, вероятно, напишу что -то, что уже существует, но я также знал, что это будут забавные упражнения и опыт обучения.
Простое начало
Что вам нужно?
- Go (любая версия сделает для этой статьи)
- Некоторые знания и терминал Unix
- Minikube
- Kubectl
Докер
IDE или текстовый редактор (я взаимозаменяемо использую VIM и VSCODE на протяжении всей этой статьи)
Linux (Ubuntu 18.04 LTS — это дистрибутив, который я использую)
С чего начать?
У меня есть некоторый опыт работы с такими инструментами сборки, как Jenkins, и у меня также есть некоторый опыт работы с автоматическим конвейером сборки, привязанного к инструменту автоматического развертывания.
Таким образом, используя идеи и контекст, которые у меня были из этих инструментов. Я начал просто, я проигнорировал Heroku на данный момент и написал простой сценарий оболочки, который создаст мою базу кода GO, а затем на основе аргумента командной строки, создать и пометить изображения Docker.
Я расширил сценарий оболочки, чтобы включить kubectl
командование для развертывания в Minikube после создания изображения Docker.
Я понял, что теперь, когда у меня был этот сценарий оболочки, я, по сути, создал работу и создание работы для моего приложения. Поэтому я начал собирать приложение Go для автоматизации запуска этого сценария на основе триггера.
Как именно вы это сделали?
Чтобы проиллюстрировать, как я сделал это, не усложнив этот пост с чрезмерными техническими деталями, я создал небольшой контрольный список, чтобы следовать, что эта статья расширяет:
а Создайте небольшое приложение GO, которое обслуживает HTML -страницу b. Задачи на это! в Напишите простой сценарий Bash, который создает изображение Docker d. Нажмите изображение Docker в Minikube и посмотрите, как он работает e. Напишите небольшой инструмент командной строки, который автоматически запускает скрипт
Создание тестового приложения
Давайте создадим пример приложения Go, которое запускает простой файловый сервер и обслуживает простую HTTP -страницу (которая использует bulma css , чтобы сделать его немного лучше) на ‘/’
Веб -сервер выглядит так:
package main import ( "log" "net/http" "os" ) func main() { // for heroku since we have to use the assigned port for the app port := os.Getenv("PORT") if port == "" { // if we are running on minikube or just running the bin defaultPort := "3000" log.Println("no env var set for port, defaulting to " + defaultPort) // serve the contents of the static folder on / http.Handle("/", http.FileServer(http.Dir("./static"))) http.ListenAndServe(":" + defaultPort, nil) } else { http.Handle("/", http.FileServer(http.Dir("./static"))) log.Println("starting server on port " + port) http.ListenAndServe(":" + port, nil) } }
HTML -файл в /статический
довольно просто:
CI/CD Example Example CI/CD Stuff
Changes should make this automatically redeploy to our local test environment
Отлично, теперь, если мы построим ( go build
) Это приложение GO и запустить его, мы должны увидеть следующее в Localhost: 3000
Контейнеризация
Теперь у нас есть приложение, которое действительно круто и делает целую кучу вещей (ничего), но это не так. Мы сосредоточены на том, как заставить этого маленького парня построить себя. Перво -наперво, нам нужно сделать это.
Если у вас нет Docker, проверьте их документы здесь ( https://docs.docker.com/install/ )
Давайте создадим следующий простой 2-этап DockerFile:
# Stage 1 FROM golang:alpine as builder RUN apk update && apk add --no-cache git RUN mkdir /build ADD . /build/ WORKDIR /build RUN go get -d -v RUN go build -o cicdexample . # Stage 2 FROM alpine RUN adduser -S -D -H -h /app appuser USER appuser COPY --from=builder /build/ /app/ WORKDIR /app CMD ["./cicdexample"]
Этот DockerFile сначала создает изображение застройщика с всем содержимым локального каталога, скопированного в каталог под названием /строить
на рисунке. Затем он получает зависимости для нашего маленького приложения и создает его — создавая двоичный файл под названием CICDEXAMER
Второй этап на самом деле создает изображение, которое мы в конечном итоге запустим. Мы используем базовое альпийское изображение и создаем пользователя под названием Appuser
который использует каталог под названием /приложение
как это домашний каталог. Затем мы копируем содержимое /build
каталог из изображения строителя в /приложение
каталог на альпийском изображении, установите рабочий каталог на /приложение
И запустите бинарный, который мы только что скопировали. Важно отметить, что мы копируем все /build
Содержимое каталога, так как нам нужен каталог статических ресурсов, который не является частью бинарного.
Как только мы создали Dockerfile и сможем успешно запустить Docker Build -t Пример: тест.
который производит (выход слегка обрезан):
Sending build context to Docker daemon 7.638MB ... Successfully built 561ee4597a93 Successfully tagged example:test
Мы готовы двигаться дальше.
Избивая вещи в форму
Далее нам нужен сценарий для автоматизации процесса создания этого изображения Docker для нас. Но мы не хотим просто создать изображение с помощью скрипта, мы хотим создать цикл обратной связи, используя иди строить
Так что мы не тратим время на настройку изображения Docker, когда наш код даже не компилируется.
Технически мы не должны отмечать успех из результата иди строить
, но скорее сделайте ошибку, перенаправленную на файл, который мы можем затем проверить на любой контент, и сбой сигнала, если есть что -нибудь в Stderr
в результате идти строить
Логические шаги для этого скрипта лучше всего представлены графически:
Сценарий выглядит следующим образом (с хорошими временными метками и выводом командной строки):
#!/bin/bash # Timestamp Function timestamp() { date +"%T" } # Temporary file for stderr redirects tmpfile=$(mktemp) # Go build build () { echo "⏲️ $(timestamp): started build script..." echo "🏗️ $(timestamp): building cicdexample" go build 2>tmpfile if [ -s tmpfile ]; then cat tmpfile echo "❌ $(timestamp): compilation error, exiting" rm tmpfile exit 0 fi } # Deploy to Minikube using kubectl deploy() { echo "🌧️ $(timestamp): deploying to Minikube" kubectl apply -f deploy.yml } # Orchestrate echo "🤖 Welcome to the Builder v0.2, written by github.com/cishiv" if [[ $1 = "build" ]]; then if [[ $2 = "docker" ]]; then build buildDocker echo "✔️ $(timestamp): complete." echo "👋 $(timestamp): exiting..." elif [[ $2 = "bin" ]]; then build echo "✔️ $(timestamp): complete." echo "👋 $(timestamp): exiting..." else echo "🤔 $(timestamp): missing build argument" fi else if [[ $1 = "--help" ]]; then echo "build - start a build to produce artifacts" echo " docker - produces docker images" echo " bin - produces executable binaries" else echo "🤔 $(timestamp): no arguments passed, type --help for a list of arguments" fi fi
Запуск его с различными аргументами дает нам следующее:
./Build Build Bin
(сбой компиляции)
🤖 Welcome to the Builder builder v0.2, written by github.com/cishiv ⏲️ 16:40:47: started build script... 🏗️ 16:40:47: building cicdexample # _/home/shiv/Work/dev/go/cicdexample ./main.go:25:1: syntax error: non-declaration statement outside function body ❌ 16:40:47: compilation error, exiting
./Build Build Docker
(слегка обрезанный выход)
🤖 Welcome to the Builder builder v0.2, written by github.com/cishiv ⏲️ 16:40:05: started build script... 🏗️ 16:40:05: building cicdexample 🐋 16:40:06: building image example:test Sending build context to Docker daemon 7.639MB ... Successfully tagged example:test ✔️ 16:40:06: complete. 👋 16:40:06: exiting...
Это кажется разумным, мы можем скомпилировать наш код и создать изображение Docker с одной командой.
Прежде чем перейдем к следующему шагу, давайте пока посмотрим на наш контрольный список:
а Создайте небольшое приложение GO, которое обслуживает HTML -страницу ✔ беременный Задачи на это! ✔ в Напишите простой скрипт Bash, который создает изображение Docker ✔ дюймовый Нажмите изображение Docker в Minikube и посмотрите, как он работает e. Напишите небольшой инструмент командной строки, который автоматически запускает скрипт
Minikube-ing
Наконец, у нас есть какая -то автоматизация для нашего конвейера сборки, но это еще не дает нам возможности проверить наше приложение.
Введите Minikube, чтобы сохранить день (своего рода).
Мы хотим, чтобы наш тестовый конвейер был следующим образом:
Если у вас нет установки Minikube, вы можете проверить документы здесь ( https://kubernetes.io/docs/tasks/tools/install-minikube/ ) о том, как его запустить и запустить.
Вам также нужно будет захватить kubectl Анкет
Как только Minikube установлен, он так же просто, как запуск Minikube Start
Чтобы запустить один кластер узлов.
Следующим шагом является настройка развертывания Kubernetes для нашего приложения, которое мы можем протолкнуть в кластер Minikube.
Поскольку это не статья о Kubernetes, я буду держать этот шаг коротким. Мы хотим иметь Deploy.yml
Файл, где мы можем сказать Kubernetes создать развертывание и услугу для нашего приложения. Было бы лучше иметь отдельные файлы для создания развертывания и сервиса, но для этого примера мы просто воссоздаем их обоих каждый раз, когда хотим перераспределить.
Итак, нам нужен следующий файл.
apiVersion: apps/v1 kind: Deployment metadata: name: example spec: selector: matchLabels: app: example tier: example track: stable template: metadata: labels: app: example tier: example track: stable spec: containers: - name: example image: "example:test" ports: - name: http containerPort: 3000 --- apiVersion: v1 kind: Service metadata: name: example spec: type: NodePort selector: app: example tier: example ports: - protocol: TCP port: 3000 targetPort: http nodePort: 30000
Мы говорим Kubernetes создать услугу под названием Пример
Это выставлено на Nodeport 30000 (так что мы можем получить к нему доступ через URL, который не меняется каждый раз, когда мы воссоздаем услугу), с приложением, работающим на порту 3000 в контейнере.
На этом этапе, поскольку мы указали Nodeport в нашем дескрипторе развертывания, мы должны быть в состоянии просто обновить нашу веб -страницу и увидеть наши изменения.
Чтобы развернуть приложение в кластер, запустите следующую команду в терминале:
./Build Build Docker && Kubectl Apply -f deploy.yml
Теперь приложение должно быть живет на кластере Minikube и выставлена на NodePort.
Чтобы получить URL для приложения, запустите эту команду:
Пример обслуживания minikube -url
Вы должны получить URL, похожий на это:
http://10.0.0.101:30000
Пример
Имя службы, которое мы указали в нашем Deploy.yml
файл.
Если мы перейдем к нашему URL, мы должны увидеть наше приложение.
Теперь мы можем проверить наш трубопровод в первый раз.
Сделайте изменение кода на index.html
и беги:
./Build Build Docker && Kubectl Apply -f deploy.yml
Вы можете обнаружить, что это еще не работает. Выходы команды Kubectl:
deployment.apps/example unchanged service/example unchanged
И правильно, поскольку мы не помечаем наше изображение Docker по -разному каждый раз, когда мы его создаем — Kubernetes на самом деле не признает, что наш код изменился. Быстрый взлом, чтобы исправить это, так как мы тестируем локально, — изменить нашу команду в следующее:
./Build Build Docker && Kubectl Delete Deployment Пример && kubectl Delete Service Пример && kubectl Apply -f deploy.yml
Это позволяет нам чисто воссоздавать услуги и развертывание каждый раз, когда мы заново запускаем сценарий. Важно отметить, что это очень большой взлом, и лучший способ сделать это — это пометить изображения Docker по -разному каждый раз, когда они производятся, и обновлять Deploy.yml
Файл с правильным тегом изображения Docker.
Так бег:
./Build Build Docker && Kubectl Delete Deployment Пример && kubectl Delete Service Пример && kubectl Apply -f deploy.yml
Позволит нам увидеть изменение, которое мы внесли в наш HTML.
Это кажется разумным, однако, чтобы немного почистить его, давайте добавим дополнительное kubectl
Команды нашему сценарию Bash.
Это достаточно просто сделать, добавив следующую функцию и слегка внести изменения в условную логику, что позволяет получить развернуть
Параметр, который должен быть передан в ./Build Build ...
Команда:
Функция:
# Deploy to Minikube using kubectl deploy() { echo "🌧️ $(timestamp): deploying to Minikube" kubectl delete deployment example kubectl delete service example kubectl apply -f deploy.yml }
Условная логика:
if [[ $1 = "build" ]]; then if [[ $2 = "docker" ]]; then if [[ $3 = "deploy" ]]; then build buildDocker deploy else build buildDocker fi echo "✔️ $(timestamp): complete." echo "👋 $(timestamp): exiting..."
Теперь мы можем запустить: ./Build Build Docker развертывание
Чтобы довольно легко привести код, все время от компиляции до развертывания! (с временем поворота ~ 1)
Последний акт: настоящая автоматизация
Наконец, мы хотим обернуть сценарий Bash, который мы создали в специально созданное приложение GO, которое автоматизирует этот процесс.
Мы должны определить триггер для нашего конвейера сборки. На данный момент есть 2 варианта:
- Сборки запускаются при сочетании истекающих времени и изменений файлов.
- Сборки запускаются при коммитах в репозиторий GIT
Чтобы проиллюстрировать эту концепцию без особых технических накладных расходов, мы пойдем с первым вариантом
По сути, это то, что мы хотим написать:
Я не буду вставлять здесь весь исходный код, однако я буду обсуждать несколько ключевых моментов относительно него (полный код можно найти Здесь )
Нам нужно решить 5 проблем:
- Какие хэши мы собираемся использовать для файлов?
- Как часто должно быть совершено пересчечность хэша?
- Как мы проводим опросы на интервале?
- Как запустить наш сценарий в контексте приложения GO?
- Какие есть условия гонки?
Первая проблема решается довольно кратко в Go, мы можем рассчитать хэш SHA256 для файла только со следующим фрагментом кода:
func CalculateHash(absoluteFilePath string) string { f, err := os.Open(absoluteFilePath) HandleError(err) defer f.Close() h := sha256.New() if _, err := io.Copy(h, f); err != nil { log.Fatal(err) } return hex.EncodeToString(h.Sum(nil)) }
Вторая проблема имеет нетривиальный ответ и он специфичен для вашего варианта использования, однако она должна быть достаточно разумной, чтобы пересматривать хэши каждые 15 секунд или около того — это означает, что у нас должно быть автоматическое развертывание, выполняемые каждые 15 секунд, если произошло изменение кода в этом окне.
Я запустил несколько критериев в реальном времени, которое необходимо запустить сборку в приложении GO, чтобы мы могли сделать образованное предположение о том, как часто для опроса для изменений файлов.
Фрагмент, который я использовал для запуска эталона, выглядит следующим образом:
func stopwatch(start time.Time, name string) { elapsed := time.Since(start) log.Printf("%s took %s", name, elapsed) }
Просто добавьте Отложить секундомер (время. Теперь (), "тест")
В верхней части функции вы хотите сравнить!
После теста я подсчитал, что нам нужно разрешить 7 секунд для завершения прошлой сборки и 8 секунд накладных расходов для неожиданных. Что дает нам в общей сложности 15 секунд.
Задача № 3 решается путем определения следующей функции и используя ее, как проиллюстрировано:
package main import ( "fmt" "time" ) func main() { go DoEvery(10*time.Second, f, "test") for {} } func DoEvery(d time.Duration, f func(time.Time, string), action string) { for x := range time.Tick(d) { f(x, action) } } func f(t time.Time, action string) { fmt.Println(action) }
Мы просто создаем функцию, которая будет вызываться каждые X секунд.
4 -я проблема также имеет довольно простое и эффективное решение в Go, мы можем определить действие строка
и запустить его, используя ОС/EXEC
упаковка в Go.
package main import ( "bytes" "log" "os/exec" ) func main() { runAction("./build build docker deploy") } func runAction(action string) { log.Println("Taking action, running: " + action) cmd := exec.Command("/bin/sh", "-c", action) var outb, errb bytes.Buffer cmd.Stdout = &outb cmd.Stderr = &errb err := cmd.Run() if err != nil { log.Printf("error") } log.Println(outb.String()) log.Println(errb.String())
Последняя проблема является важной, одно явное условие гонки заключается в том, что если мы отслеживаем хэши для всех файлов в каталоге, то мы, скорее всего, пойдем в какой -то рекурсивный цикл сборки, поскольку мы активно меняем файлы Монитор (путем создания бинарного). Мы можем одолжить концепцию из git
Здесь и реализуйте Белый список
, то есть список файлов, которые следует игнорировать в нашем хэш -расчете. Что -то на это влияние,
var whiteList []string func CreateWhiteList() { file, err := os.Open("./.ignore") if err != nil { log.Println("no .ignore file found, race condition will ensue if jobs edit files -- will not create whitelist") } else { defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { log.Println(scanner.Text()) whiteList = append(whiteList, scanner.Text()) } if err := scanner.Err(); err != nil { log.Fatal(err) } } }
Сочетая решения этих проблем, мы можем произвести это)
Строительство строителя с Go Build -o Pipeline
приведет к бинарному под названием трубопровод
Анкет
Затем мы можем перенести этот бинар в наш рабочий каталог. Нам также нужно создать игнорировать
Файл в нашем рабочем каталоге, чтобы игнорировать бинарную, произведенную строителем для нашего приложения.
игнорировать
Файл просто:
pipeline cicdexample
Мы игнорируем .git
Программатически, поскольку это никогда не то, что мы хотим включить.
Мы можем наконец запустить наш строитель
./трубопровод
Внесение изменения в index.html
Из нашего приложения следует начать автоматизированную сборку и перераспределение, точно так же, как мы намеревались сделать.
Как вы можете видеть из этого вывода:
shiv@shiv-Lenovo-ideapad-310-15IKB:~/Work/dev/go/cicdexample$ sudo ./pipeline 2019/12/24 18:23:42 map[] 2019/12/24 18:23:42 creating whitelist 2019/12/24 18:23:42 pipeline 2019/12/24 18:23:42 cicdexample 2019/12/24 18:23:42 builder 2019/12/24 18:23:42 building registry 2019/12/24 18:23:42 starting directory scan 2019/12/24 18:23:42 pipeline is whitelisted, not adding to registry 2019/12/24 18:23:42 computing hashes & creating map entries 2019/12/24 18:23:57 verifying hashes 2019/12/24 18:24:12 verifying hashes 2019/12/24 18:24:12 ./static/index.html old hashbebc0fe5b73e2217e1e61def2978c4d65b0ffc15ce2d4f36cf6ab6ca1b519c17new hash16af318df74a774939db922bcb4458a695b9a38ecf28f9ea573b91680771eb3achanged detected - updating hash, action required 2019/12/24 18:24:12 Taking action, running: ./build build docker deploy 2019/12/24 18:24:20 🤖 Welcome to the Builder builder v0.2, written by github.com/cishiv ⏲️ 18:24:12: started build script... 🏗️ 18:24:12: building cicdexample 🐋 18:24:13: building image example:test Sending build context to Docker daemon 10.11MB ... Successfully tagged example:test 🌧️ 18:24:19: deploying to Minikube deployment.apps "example" deleted service "example" deleted deployment.apps/example created service/example created ✔️ 18:24:20: complete. 👋 18:24:20: exiting... 2019/12/24 18:24:20 2019/12/24 18:24:20 -------------------------------------------------------------------------------- 2019/12/24 18:24:27 verifying hashes
Вот и все! Были сделаны. Мы успешно написали, полезный, хотя и простой трубопровод CI/CD для улучшения нашего процесса разработки.
Каковы выводы из этого опыта?
Советы
Может быть заманчиво использовать ранее существовавшее решение для такого рода трубопровода, но я рекомендую, что если у вас есть время и энергия, чтобы написать небольшое решение проблемы, с которой вы столкнулись, вы определенно должны. Это заставляет вас думать о том, как ваши приложения (ы) работают в производственных условиях, а также о том, как может быть улучшен ваш процесс разработки. Это также очень весело.
Расширения
Есть много возможных расширений в этом проекте, некоторые из которых я хочу заняться скоро, я перечислил некоторые из самых интересных здесь:
- Разрешить создание сценариев сборки через приложение Builder
- Постройте из VCS (то есть GIT клонировать репо и построить его на основе описания работы)
- Пользовательский интерфейс для этого конвейера сборки
- Запустите приложение Builder в Docker на самом Minikube
- Размещайте приложение Builder на облачной платформе, а также настраиваются развертывания и сборки
- Пусть пользователи создают
.json
файлы заданий для сборки - Многоязычная поддержка
Предостережения
Этот вид быстрой и грязной трубопроводы не лучше, чем ранее существовавшее решение, но его гораздо веселее использовать, так как я могу быстро внести в него изменения в зависимости от потребности, которую я могу иметь.
Я относительно новичок, чтобы идти, Kubernetes и Docker. Таким образом, стиль, который я использовал, может быть не лучшей практикой, но он работает для меня.
Примеры использования
В настоящее время я использую аналогичный конвейер для автоматизации развертывания проекта, над которым я работаю под названием CRTX ( http://crtx.xyz/ ) — в тестовую среду, когда я пишу код. В первую очередь это база кода GO с несколькими приложениями, которые непрерывно развертываются в кластере Minikube. Этот трубопровод значительно облегчает протекание данных тестирования между приложениями.
Заключительные замечания
Это первый раз, когда я написал техническую статью и хотел бы услышать отзывы о подходе, который я принял, а также о фактическом содержании!
Я планирую написать больше об инструментах, которые я использую и строю, поэтому, если вам понравилось читать это, пожалуйста, дайте мне знать!
Вы можете дать мне отзыв о Twitter или прямо здесь, в комментариях ✍ ️
Оригинал: «https://dev.to/cishiv/building-a-simple-ci-cd-pipeline-for-local-testing-using-go-docker-minikube-and-a-bash-script-1647»