Пошаговая настройка Jenkins для iOS в 2023 году | Полное руководство

Я потратил больше 3 недель настраивая Jenkins в первый раз. Не повторяйте моих ошибок!

Сейчас 2023 год, и вы и ваша команда решили отказаться от текущего CI, который у вас есть, в пользу автономного Jenkins CI. Отлично! В этой статье мы не будем рассматривать плюсы / минусы использования одной системы CI в сравнении с другой, а скорее сосредоточимся на том, как настроить полностью рабочую среду Jenkins CI для iOS.

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

Я много экспериментировал и в течение нескольких недель боролся за то, чтобы все было работало правильно, особенно после того, как получил множество загадочных ошибок наряду с проблемами, которые возникали время от времени. На удивление, не найдя исчерпывающего руководства по “Лучшим практикам Jenkins”, я решил собрать его воедино, чтобы поделиться знаниями, которые я получил на собственном горьком опыте. 🥲

Предварительные условия

Я не буду писать то, что вам нужна машина с macOS. В этой статье предполагается, что у вас уже есть компьютер, работающий под управлением macOS (да, требуется macOs), и что вы уже установили Jenkins. Если же вы еще не установили Jenkins, вот официальное руководство (этот шаг прост):https://www.jenkins.io/doc/book/installing/macos

Кроме того, в этой статье мы будем использовать Homebrew, rbenv, xcodes, и Bundler. Я не буду вдаваться в подробности о том, почему я рекомендую каждый из них (возможно, в следующем посте 😉), но не стесняйтесь обращаться ко мне, если вам интересно!

Установка зависимостей

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

Обновление вашего ~/.zshrc файла

Эти настройки должны помочь вам запуститься, а также обеспечить меры безопасности для fastlane. Добавьте это в свой файл ~/.zshrc:

# Initialize rbenv if it's already installed export PATH=$PATH:/usr/local/bin:$HOME/.rbenv/bin:$HOME/.rbenv/shims if which rbenv > /dev/null; then   eval "$(rbenv init -)" fi # Set these according to your project's needs/configuration export XCODE_VERSION="14.3" export BUNDLER_VERSION="2.2.32" export RUBY_VERSION="3.1.2" # For security measures, ask fastlane to not store your fastlane password. # Even though your CI workflows and scripts probably doesn't even use App Store Connect User+Password anymore, if you happen to run fastlane manually in the CI machine, you don't want it to accidentally store your own Apple ID password in its Keychain. export FASTLANE_DONT_STORE_PASSWORD="1" # Required by fastlane (to prevent issues with unicode) export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 export LANGUAGE=en_US.UTF-8

Обратите внимание, что в зависимости от того, какого облачного провайдера вы используете (например, AWS, Azure и т.д.), в вашем существующем файле ~ /.zshrc может уже содержаться некоторое содержимое, поэтому просто добавьте приведенный выше фрагмент в конец файла, чтобы избежать проблем.

После обновления вашего ~/.zshrc файла запустите его, чтобы применить изменения (или завершите сеанс SSH и запустите новый):

source ~/.zshrc

Затем вы можете скопировать и вставить этот фрагмент в свой терминал, чтобы упростить настройку зависимостей:

echo "Installing rbenv and the right ruby version that your project uses" brew install rbenv ruby-build rbenv install $RUBY_VERSION rbenv global $RUBY_VERSION echo "Speeding up gem installs" echo "gem: --no-document" >> ~/.gemrc echo "Initializing rbenv (will run the initialization code that we just saved in the ~/.zshrc file)" source ~/.zshrc echo "Installing bundler" gem install rubygems-update gem update --system gem install bundler -v $BUNDLER_VERSION echo "Installing xcodes" brew install xcodesorg/made/xcodes echo "Installing the Xcode version your team uses" echo "Note that this one is gonna take a long while (maybe 10-20 minutes). Take a break, and once it's, done you're gonna need to enter the sudo password so the installation completes" brew install aria2 xcodes install $XCODE_VERSION --experimental-unxip --select --update

Настройка ваших учетных данных git

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

Настройка пайплайнов

Существует условие гонки (race condition) в том, как Дженкинс выбирает, какие ветки создавать, при использовании различных стратегий для “обнаружения” веток (т.е. “Исключить ветки, которые также зарегистрированы как pull request” VS “Только ветки, которые также зарегистрированы как pull request”). Это вызывает такие проблемы, как то, что pull requests навсегда застревают в состоянии “ожидания”, что блокирует их объединение. По этой причине нам понадобятся 2 пайплайна:

  • Один пайплайн создаст все ветки, которые не предназначены для подачи в качестве pull request, например main, master, development, staging, production (зависит от того, как вы их называете).
  • Второй пайплайн - для создания всех остальных веток, которые могут стать pull request.

Мне потребовалось много времени, чтобы разобраться в этом, так как примерно в 5-10% случаев pull requests зависали и не могли быть объединены, потому что они впадали в какое-то странное состояние. Это было единственное полностью работающее решение, которое я нашел для решения этой проблемы.

Настройка пайплайнов, которая будет создавать все ветки, которые не станут pull request

Перейдите на https:///view/all/newJob для создания пайплайна. Выберите Multibranch Pipeline из предложенных вариантов:

Пошаговая настройка Jenkins для iOS в 2023 году | Полное руководство

Мне нравится добавлять тип проекта (в данном случае Multibranch Pipeline) к названию пайплайна, чтобы я знал, какую структуру он использует, просто по названию. Таким образом, в данном случае я бы назвал это как-то вроде protected-branch-multibranch-pipeline 😊

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

  • Учетные данные GitHub: выберите учетные данные GitHub, которые вы добавили в разделе “Настройка ваших учетных данных git” выше.
  • Добавьте новое поведение “Обнаруживать ветки” и выберите “Все ветки”.Добавьте фильтр “Фильтровать по имени (с помощью регулярного выражения)” в этом разделе с текстом “master” или “(master|staging|production)”.
  • Периодически, если нет иного способа, запускайте:Интервал: 1 минутаЧестно говоря, это относится к тем случаям, когда Jenkins не всегда собирал сборки, необходимые для запуска. Но это было в самом начале процесса, еще до того, как я начал использовать приложение GitHub, поэтому я не уверен, можно ли отключить это приложение.

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

Настройка пайпланов, которые будут создавать pull request

Посетите https:///view/all/newJob еще раз, чтобы создать новый пайплайн, и выберите Multibranch Pipeline из опций. Опять же, я порекомендую назвать его как-нибудь вроде pull-requests-multibranch-pipeline, чтобы вы могли легко идентифицировать его в будущем.

Выполните те же действия, что и выше, за исключением того, что поведение, которое вы собираетесь добавить, - это “Обнаруживать pull request из origin”, выбрав стратегию как “Ревизия текущих pull request“:

Пошаговая настройка Jenkins для iOS в 2023 году | Полное руководство

Настройка вашего Jenkinsfile файла

Вы же не подумали, что я пропущу самую важную часть?

В описанных выше шагах при настройке нового пайплайна вам нужно было выбрать путь к вашему Jenkinsfile файлу. Способ настройки Jenkins, описанный в этой статье, должен быть согласован с конкретным способом настройки вашего Jenkinsfile и ваших CI-скриптов, поэтому я расскажу о них ниже.

Вот шаблон, который вы можете использовать для основного файла Jenkinsfile, который можно использовать для запуска как protected-branch-multibranch-pipeline, так и pull-requests-multibranch-pipeline:

pipeline {     agent any     options {         ansiColor('xterm') // Adds color to logs, enable via https://github.com/jenkinsci/ansicolor-plugin         timeout(time: 8, unit: 'HOURS') // Set the timeout limit for builds         disableConcurrentBuilds(abortPrevious: true) // Cancel the previous build upon pushing newer commits in the same branch     }     environment {         // Set up all your secrets (aka credentials) here, e.g. API keys for fastlane, danger, etc.         APP_STORE_CONNECT_API_KEY_ISSUER_ID = credentials('APP_STORE_CONNECT_API_KEY_ISSUER_ID')         APP_STORE_CONNECT_API_KEY_KEY = credentials('APP_STORE_CONNECT_API_KEY_KEY')         APP_STORE_CONNECT_API_KEY_KEY_ID = credentials('APP_STORE_CONNECT_API_KEY_KEY_ID')         DANGER_GITHUB_API_TOKEN = credentials('DANGER_GITHUB_API_TOKEN')         MATCH_PASSWORD = credentials('MATCH_PASSWORD')         // …etc     }     stages {         stage("1. Set Up") {             steps {                 withCredentials([usernamePassword(credentialsId: '_github_app', usernameVariable: 'GITHUB_APP_USERNAME_TOKEN', passwordVariable: 'GITHUB_APP_PASSWORD_TOKEN')]) {                     sh '''                     source ~/.zshrc # Needs to be run to set up the right PATH env var, initialize rbenv, and everything else we configured previously in the ~/.zshrc file                     # Refs.: https://git-scm.com/docs/gitcredentials#_custom_helpers and https://stackoverflow.com/q/61146986/4075379                     git config credential.username ${GITHUB_APP_USERNAME_TOKEN}                     git config credential.helper "!echo password=${GITHUB_APP_PASSWORD_TOKEN}; echo"                     # From now on you can add your scripts here, e.g. make, bundle install, pod install, xcodebuild build and test, etc.                     make                     '''                 }             }         }         stage("2. Static Code Analysis") {             steps {                 sh '''                 source ~/.zshrc # Yes, unfortunately you need to run this every time you declare a new "sh" shell script in your Jenkinsfile.                 bundle exec rake danger                 '''             }         }         stage("3. Build & Distribute") {             steps {                 withCredentials([usernamePassword(credentialsId: '_github_app', usernameVariable: 'GITHUB_APP_USERNAME_TOKEN', passwordVariable: 'GITHUB_APP_PASSWORD_TOKEN')]) {                     sh '''                     source ~/.zshrc                     bundle exec fastlane archive_and_distribute # This action needs access to GITHUB_APP_USERNAME_TOKEN and GITHUB_APP_PASSWORD_TOKEN env vars directly                     '''                 }             }         }     }     post {         // Always clean your workspace after you finish using it, otherwise after a few builds you will end up with a full disk and your machine will likely die.         always {             cleanWs(cleanWhenAborted: true, cleanWhenFailure: true, cleanWhenNotBuilt: true, cleanWhenSuccess: true, cleanWhenUnstable: true, deleteDirs: true)         }     } }

Объяснение функции “withCredentials”

Работы функции “withCredentials” заключается в следующем:

// 1st parameter is the name of your GitHub App credentials ID, as you registered in Jenkins. // 2nd and 3rd parameters are variable names you're declaring now, so you can name them whatever, but you will need to reference them later, so make them relatable. withCredentials([usernamePassword(     credentialsId: '_github_app',     usernameVariable: 'GITHUB_APP_USERNAME_TOKEN',     passwordVariable: 'GITHUB_APP_PASSWORD_TOKEN' )])

Вводимые данные - это идентификатор учетных данных, который вы зарегистрировали ранее в своих учетных данных Jenkins. Это дает доступ к этому плагину Jenkins, чтобы иметь возможность генерировать эфемерные токены (имя пользователя и пароль), которые затем можно использовать для отправки HTTPS-запросов в GitHub. Для вас критически важно понимать, что это HTTPS auth, а не SSH — поэтому, если у вас есть команды git, использующие SSH в пайплайне, вам следует определить, что вы работаете в среде CI, и переключить их на использование HTTPS вместо этого. Распространенным примером этого в среде iOS является URL-адрес Git, используемый fastlane match.

В первый раз, когда мы генерируем такие эфемерные учетные данные, мы предоставляем их git config credential.helper, так что любая операция git (использующая HTTPS) после этого момента (даже на других этапах) сможет выполняться без повторного запроса имени пользователя и пароля. В приведенном выше примере вы можете наблюдать это на этапе 2, где мы вызываем danger (который публикует комментарии на GitHub, поэтому ему нужны учетные данные), и нам не нужно было генерировать для него новые учетные данные.

Отличается от этапа 3, где нам нужен прямой доступ к учетным данным env vars, и переменные env не сохраняются от одного этапа к другому, поэтому мы повторно генерируем учетные данные, таким образом, снова устанавливая значения для переменных env.

Если вам интересно, вот как вы могли бы настроить действие fastlane match в вашем Fastfile:

git_url = is_ci? ? "https://#{ENV["GITHUB_APP_USERNAME_TOKEN"]}:#{ENV["GITHUB_APP_PASSWORD_TOKEN"]}@github.com/myorg/myrepo.git" : "git@github.com:myorg/myrepo.git" match(git_url: gir_url)

Примечания:

  • У вас должен быть плагин GitHub Branch Source версии 2.7.1 или выше, чтобы использовать эти API. Эта функция была введена и анонсирована в 2020 году.
  • Токен API, который вы получите, будет действителен только в течение одного часа. Поэтому не получайте его в начале пайплайна и не рассчитывайте на то, что он будет действителен на протяжении всего процесса.

Последние штрихи

Осталось сделать две вещи, прежде чем вы закончите свое путешествие по настройке Jenkins.

Сборки застревают навсегда

Это может произойти, когда ваш пайплайн пытается получить доступ к git внутри пайплайна (например, при запуске pod install или извлечении SPMS и т.д.). Это может быть вызвано тем, что ваш компьютер запрашивает аутентификацию по паролю Keychain, но это не отображается в журналах Jenkins. Это прискорбно, но единственное решение, которое, как я обнаружил, полностью работает в этом случае, - это войти в компьютер с доступом к пользовательскому интерфейсу (например, VNC, а не SSH) и нажать “Всегда разрешать" во всплывающем окне, которое запрашивает разрешение на доступ к “логину" Keychain. При появлении запроса введите пароль root для компьютера macOS.

Вам нужно сделать это только один раз, и больше никогда.

Учетные данные Xcode git противоречат учетным данным приложения GitHub

Существует проблема, которая приводит к сбою сборки с ошибкой:

stderr: remote: Invalid username or password

Это происходит примерно в 5-10% сборок, ломая их. Оказывается, учетные данные git Xcode могут конфликтовать с теми, которые мы устанавливаем, и тогда они ломаются. Чтобы исправить это, просто следуйте моему ответу на этот вопрос StackOverflow: Jenkins Github Authentication fails sometimes?

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

Вывод

В этом длинном руководстве вы узнали о весьма самонадеянном способе настройки Jenkins на вашей компьютере, адаптированном для среды iOS. Если вы работаете с другими средами, большинство советов, приведенных в этой статье, также помогут вам встать на правильный путь, например, для Android, React, Flutter, Node и т.д. Что изменится, так это, вероятно, только ваши зависимости и конкретные примеры, которые я приводил при настройке Jenkinsfile вашего файла.

Когда я впервые настроил свою первую Jenkins машину, я получил свою первую сборку за считанные часы, но это была далеко не та среда, которая соответствовала бы потребностям команды. Проверки на GitHub обязательны, например (что-то, что непросто настроить), и среда должна быть на 100% стабильной, без сбоев, в противном случае ваша команда не будет доверять системе CI.

Другими словами, я выполнил 80% работы очень быстро, но на оставшиеся 20%, чтобы получить отточенный конвейер CI, у меня ушли буквально недели. Я надеюсь, что, следуя этому руководству, вам не придется беспокоиться об окружающей среде и вы сможете сосредоточиться на том, что важно: построении идеального пайплайна для вашего проекта и вашей команды 🤗

В моем следующем посте в блоге я расскажу о том, что вы можете сделать, чтобы сохранить ту тяжелую работу, которую вы проделали при настройке вашей Jenkins машины. Проще говоря: как сохранить резервную копию вашего CI! Оставайтесь с нами.

Подписывайся на наши соцсети: Telegram / VKontakte

Вступай в открытый чат для iOS-разработчиков: t.me/swiftbook_chat

11
Начать дискуссию