Slide 1

Slide 1 text

AWSとJenkinsを活用して 1年間で約500回商用デプロイした話と Kubernetes活用 Jenkinsユーザ・カンファレンス東京 2018 #Jenkinsstudy #juc2018 @takamii228

Slide 2

Slide 2 text

自己紹介 ● @takamii228 ○ SIerのAgile開発を頑張る部署に所属 ○ 認定スクラムマスター ○ アジャイル開発における開発基盤整備やプロジェクト支援に従事 ● なんでもやる雑食エンジニア ○ CI/CD環境構築、アーキ設計、 AWS設計、Scrum運営、チームビルディング ○ 仕事でよく使う言語は Java ( Spring )、PHP ( Laravel ) ○ 開発基盤(GitLab, Jenkins, Mattermost, JIRA, Conflu等) on k8sを運用中 

Slide 3

Slide 3 text

とあるプロジェクトの、何の数字でしょうか? 491 1592 1991

Slide 4

Slide 4 text

とあるプロジェクトの、何の数字でしょうか? ● 491 ○ 商用環境へデプロイするJenkinsジョブの実行回数( 約1年間 ) ● 1592 ○ 検証環境へデプロイするJenkinsジョブの実行回数( 約2年間 ) ● 1991 ○ クローズされたPull Requestの数( 約2年間 )

Slide 5

Slide 5 text

今日主に話すこと ● 頻繁にデプロイ可能な仕組みをAWSとJenkinsでどう作ったのか ● Jenkinsの運用上の工夫や発生した問題をどのように対処したか ● なぜ頻繁に商用デプロイ可能な仕組みを構築できたのか ● 【番外編】Kubernetes上でJenkinsを使う話

Slide 6

Slide 6 text

実践する時は自分の置かれた状況で考えよう ”外から得られた学びを、そのまま自分たちの現場や仕事で適 用しようとしてもたいていうまくいかない。自分たちの「状況」に照 らし合わせてみることが必要だ。  (中略) 私たちは、他者の実践の背景にどんな状況、制約があったのか を理解し、自分たちの状況、制約の下ではどのように実践すべ きかを捉え直さないといけない。”

Slide 7

Slide 7 text

目次 1. とあるWebサービス開発プロジェクトの概要 2. 開発フローとJenkinsのビルドパイプライン 3. Jenkins運用中に起きたトラブルとその対応 4. 振り返りとまとめ 5. 【番外編】JenkinsをKubernetes上で動かす話

Slide 8

Slide 8 text

1. とあるWebサービス開発プロジェクトの概要

Slide 9

Slide 9 text

とあるWebサービス開発案件 ● B2C向け新規Web ECサービス開発 ● 既存システムと新規開発システムを含む複数のサービスをAPIで連携 ● フロントエンドや各種サービスを複数の会社がそれぞれ新規に開発

Slide 10

Slide 10 text

開発体制と会社毎の開発範囲 弊社から画面開発ベンダへ フロントエンドの実行環境と APIを提供

Slide 11

Slide 11 text

プロジェクトにおける課題 ● サービス開始までのスケジュールがタイトである ● 商用運用開始後も頻繁に変更を反映させたいという顧客要望がある ● 各社が並行新規開発のため、頻繁なAPI定義変更が予想される ● サービス間の連携が多く、特に結合試験時期に炎上が予想される

Slide 12

Slide 12 text

頻繁な変更に耐えうるアーキテクチャ設計 API定義や設定を頻繁に変更可能なアーキテクチャを設計した 1. Git と Jenkinsによる CI/CD環境を構築する 2. Swaggerを使ってインクリメンタルなAPI開発を実現する 3. AWSとAnsibleでImmutable Infrastatucreを実現する これらを踏まえたJenkinsのビルドパイプラインを構築した。

Slide 13

Slide 13 text

【参考】OpenAPI Specification(Swagger) REST APIを記述するための規格 ● OpenAPI Initiativeが企画 ● YAMLファイルでAPI定義を記述する 主要なツール ● Swagger Editor(エディタ) ● Swagger UI(ビュワー) ● Swagger Codegen(コード生成ツール)

Slide 14

Slide 14 text

https://swagger.io/tools/open-source/

Slide 15

Slide 15 text

使用したソフトウェアやライブラリ バックエンド フロントエンド 言語 Java PHP フレームワーク Spring Boot - ライブラリ Spring Cloud Swagger Codegen Flyway など SwaggerCodegen テストフレームワーク JUnit Spock PHPUnit

Slide 16

Slide 16 text

2.開発フローとJenkinsのビルドパイプライン

Slide 17

Slide 17 text

GitBucketとJenkinsを使った開発フロー ● ブランチ戦略はGitLabフローを採用 ● Pull RequestとMaster Merge時にCIを実行してMasterの品質を担保 ● ビルド環境はGitBucketとJenkins Master + Agent 3台構成

Slide 18

Slide 18 text

JenkinsfileとMultibranch Pipelineでジョブを定義 ● JenkinsのジョブはMultibranch Pipelineで定義 ● ブランチ毎に同じCIが走るようにGitレポジトリにJenkinsfileを含めた ● ジョブ定義がJenkinsfileに集約されるため管理性が高まる ● JenkinsfileはScripted Pipelineで記述 ( 当時はDeclarativeがなかった )

Slide 19

Slide 19 text

Jenkinsで3種類のJenkinsパイプラインを定義 1. Git Push時に実行するブランチビルドパイプライン 2. リリース資材を作成するリリースビルドパイプライン 3. リリース資材をAWS環境にデプロイするデプロイパイプライン

Slide 20

Slide 20 text

1. ブランチビルドパイプライン ● Pull RequestとCIを組み合わせて、CIに成功したものだけmasterにマージ ● Swaggerでのソース・ドキュメント自動生成をパイプラインに含めた ● Swagger yamlと自動生成ソース・ドキュメントの二重管理を防いだ

Slide 21

Slide 21 text

【参考】Swaggerを使ったインクリメンタルなAPI開発 https://speakerdeck.com/int128/customize-swagger-templates

Slide 22

Slide 22 text

2.リリースビルドパイプライン releaseブランチのプッシュで成果物を作成する(後述)

Slide 23

Slide 23 text

Multibranch Pipelineとtag push GitBucket / GitLabとMultiBranch Pipelineではtag push契機でジョブが実行されないため、 release/x.y.z branch の pushの中でtag pushを実行するようにした GitBucket : https://int128.hatenablog.com/entry/2016/10/06/224444 GitLab : https://takamii.hatenablog.com/entry/2018/07/09/001337 def releaseVersion = env.BRANCH_NAME.startsWith('release/') ? env.BRANCH_NAME.substring('release/'.length()) : null if(releaseVersion){ def userRemoteConfig = scm.userRemoteConfigs.head() withCredentials([usernameColonPassword(credentialsId: userRemoteConfig.credentialsId, variable: 'GIT_CREDENTIAL')]) { def url = userRemoteConfig.url.replace('://', "://${env.GIT_CREDENTIAL}@") sh 'git config user.email [email protected]' sh 'git config user.name gituser' sh "git tag -f $releaseVersion" sh "git push $url $releaseVersion -f" sh "git push $url --delete ${env.BRANCH_NAME}" }  }

Slide 24

Slide 24 text

3. デプロイパイプライン ● AWSのEC2 Base AMIとAnsibleでImmutable Infrastractureを構築 ● AutoScaling Group切り替えでBlue Greenデプロイを実現

Slide 25

Slide 25 text

【参考】オンラインデプロイパイプライン EC2をterminateしないオンラインリリース(高速デプロイ)の仕組みも作成した

Slide 26

Slide 26 text

Jenkinsによる自動化でデプロイ頻度を増やせた ● 複雑なビルド・リリース処理をJenkinsジョブで自動化した ● AWSとAnsibleを使うことでインフラ構築も自動化できた ● Jenkinsジョブ実行で誰でもいつでもデプロイできるようにできた ● デプロイのハードルが下がり、デプロイ頻度を増やすことができた

Slide 27

Slide 27 text

【参考】フロントエンド開発にも越境しました 画面開発会社にもJenkinsでのデプロイジョブを提供し、開発のアジリティが向上 https://speakerdeck.com/takamii228/xpjug2018-lt-takamii228

Slide 28

Slide 28 text

3. Jenkins運用中に起きたトラブルとその対応

Slide 29

Slide 29 text

Jenkins運用中に発生した事件簿 1. スローテスト問題によるJenkinsのビルド待ち行列 2. ビルドのゴミが溜まってDisk Fullが発生 3. Jenkinsおじさんボトルネック問題

Slide 30

Slide 30 text

スローテスト問題によるJenkinsの待ち行列 ● 開発が進むと1回のCI実行に20分近くかかるようになった ● PR発行からMasterマージ可能になるまでに1時間以上かかるケースもあった ● 開発者の開発リズムを崩す原因になっていた

Slide 31

Slide 31 text

スローテスト問題に対する解決策(1/2) SpringのテストのMockのDIの仕方を改善し、テストケースの実行速度を向上 Field InjectionからConstructor InjectionにしてApplication Contextの再ロード時間を削減 // Field Injection方式 class UserInfoServiceSpec extends ServiceSpec { @Autowired UserInfoService userInfoService; @TestConfiguration static class MockConfig { final detachedMockFactory = new DetachedMockFactory() @Bean UserInfoService pointInfoService() { detachedMockFactory.Mock(UserInfoService) } ... //Constructor Injection方式 class UserInfoServiceSpec extends ServiceSpec implements ConstructorInjectionHelper { UserInfoService userInfoService = Mock() def setup() { UserInfoService = newInstanceWithMocks(UserInfoService) } ... https://int128.hatenablog.com/entry/2017/02/16/224842

Slide 32

Slide 32 text

スローテスト問題に対する解決策(2/2) パイプラインのうち同時実行可能な部分を並列化して待ち時間を半減させた

Slide 33

Slide 33 text

【参考】Jenkinsfileによる並列ジョブ実行 parallelディレクティブで並列実行ジョブを定義できる parallel( apiA: { node { checkout scm stage('test') { withEnv(rdsEnv()) { sh './gradlew apiA:check' } } } }, apiB: { node { checkout scm stage('test') { withEnv(rdsEnv()) { sh './gradlew apiB:check' } } } }, batch: { node { checkout scm stage('test') { withEnv(rdsEnv()) { sh './gradlew batch:check' } } } }, } ) https://int128.hatenablog.com/entry/2017/07/12/131402

Slide 34

Slide 34 text

2.ビルドのゴミが溜まってDiskフルが発生問題 問題 ● Jenkinsのディスク容量が不足してジョブ実行に失敗した ● ビルドログ、Dockerビルドのゴミが残留していた ○ Docker Buildを使っている場合はイメージのゴミが残り続ける 対処 ● 【暫定】ビルド結果を定期的に削除する設定を追加した ● 【恒久】Jenkinsに対して1TBのEBSをアタッチ(お金で解決) ○ Docker Build部分はAWS CodeBuildに移行した

Slide 35

Slide 35 text

3. Jenkinsおじさんボトルネック問題 問題 ● Jenkinsのジョブがブラックボックス化し有識者しかメンテできない ● Jenkinsジョブが失敗したときの原因分析が有識者しかできない ● 有識者がボトルネックになり不在時の対応が遅れる 対処 ● Jenkins有識者をチーム内に育成する ● Jenkinsfileのジョブ定義のドキュメント作成し Confluenceで共有 ● 運用作業はJenkins有識者とペアで作業する

Slide 36

Slide 36 text

4. 振り返りとまとめ

Slide 37

Slide 37 text

なぜここまで頻繁に商用デプロイできたのか? ● JenkinsのCI/CDパイプラインを本格開発前に準備できていた ○ アーキテクチャが本格開発前に固まっていた ○ CI/CDパイプラインはコーディングの本格開始前までに準備しておくべき ● AWSを使うことで検証環境と商用環境をほぼ差分なく構築できた ○ AWSとAnsible使うことでImmutable Infrastractureが構築できた ○ 差分はあってもインスタンスタイプや環境変数程度だった ● 検証環境で実績を積んだJenkinsジョブを商用環境にそのまま利用できた ○ 検証環境で成功が保証されたものを商用環境でもそのまま利用できた

Slide 38

Slide 38 text

まとめ 頻繁にサービスをデプロイ可能にするために、 ● アーキテクチャとCI/CD環境を開発本格化前に準備・構築しておく ● 検証環境で自動デプロイの実績を積み、そのまま商用環境に持っていく ● Jenkinsの運用がブラックボックスにならないように透明化しておく

Slide 39

Slide 39 text

【番外編】JenkinsをKubernetes上で動かす話 ※他のセッションとの発表と重なる部分やK8sの詳細な説明は省略します

Slide 40

Slide 40 text

開発環境をKubernetesで運用しています ● 以前docker-composeで運用していた開発基盤をKubernetes化 ● K8s DashboardでPodのリソース・ログ確認などの運用が容易になった

Slide 41

Slide 41 text

Kubernetes Dashboardによる運用

Slide 42

Slide 42 text

● TerraformとkopsでAWS上に k8s Clusterを構築 ● Helmfileに各Podの定義を記述し Helm Chartを使ってPodをデプロイ Terraform、kops、Helmによるk8s環境構築

Slide 43

Slide 43 text

JenkinsのHelm Chart 公式のHelm Chartが用意されている https://github.com/helm/charts/tree/master/stable/jenkins helmfileにPodやJenkinsのdefault設定を上書く設定を記述する - name: jenkins namespace: devops chart: stable/jenkins values: - Master: HostName: jenkins.{{ requiredEnv "kubernetes_ingress_domain" }} resources: {requests: {cpu: 50m, memory: 256Mi}, limits: {cpu: 2000m, memory: 4096Mi}} ServiceType: ClusterIP InstallPlugins: - workflow-aggregator:2.5 - credentials-binding:1.16 - git:3.9.1 - gitlab-plugin:1.5.8 - mattermost:2.5.0 - keycloak:2.2.0 Persistence: Size: 50Gi

Slide 44

Slide 44 text

JenkinsをKubernetes上で動かす2つの方法 1. Jenkins Master、Agent共にKubernetes上のPodで動かす ○ AgentのPodはビルド実行単位で起動させる ○ Agent Podの設定はKubernetes Plugin経由で設定する 2. Jenkins MasterのみKubernetes上で動かしてAgentはk8s外で実行する ○ Agentを従来のAgentサーバで実行する ○ AWS CodeBuild、GCP CloudBuildなどのクラウドビルドサービスを使う 設定が容易でスケールメリットもあるAWS CodeBuildを採用

Slide 45

Slide 45 text

【参考】AWS CodeBuild AWSが提供するマネージドのコンテナビルドサービス ● 指定したランタイムのコンテナイメージによるビルドを実行する ● AWS ECRに登録した自作のコンテナイメージも使える ● 最大20並列で実行できる ● かかる費用はビルド時間単位の従量課金制 ● Jenkinsからはプラグイン経由でビルド実行できる https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-online-seminar-awsjenkins

Slide 46

Slide 46 text

Jenkins のジョブを AWS CodeBuildで動かす GitLabへのPush契機でAWS CodeBuildを実行し、成果物をS3に配置する https://takamii.hatenablog.com/entry/2018/09/07/234509

Slide 47

Slide 47 text

AWS CodeBuild のかゆいところ ● GitLab / GitBucketからは直接連携できないため間に Jenkinsが必要 ○ 逆にJenkinsを間に挟むことてGit Push単位での実行が可能になる ● ブランチ制御できないためMultibranch Pipelineと相性が悪い ○ ブランチの情報を環境変数で渡して if文で制御する //buildspec.yml …. package: commands: - | if [ -z "${BRANCH_NAME%%release/*}" ]; then ./package.sh fi artifacts: commands: - | if [ -z "${BRANCH_NAME%%release/*}" ]; then aws s3 cp artifacts "s3://bucket/${BRANCH_NAME#release/}/artifacts" fi https://github.com/takami228/jenkins-awscodebuild-starter //Jenkinsfile …. stages { stage('codebuild') { steps { awsCodeBuild( credentialsType: 'keys', projectName: 'projectName', region: 'aws-region', sourceControlType: 'project', sourceVersion: env.BRANCH_NAME, envVariables: "[{BRANCH_NAME,${env.BRANCH_NAME}}]", ) } }

Slide 48

Slide 48 text

おわり

Slide 49

Slide 49 text

参考資料 ● @takamii228 ○ つばくろぐ : https://takamii.hatenablog.com/ ○ SpeakerDeck : https://speakerdeck.com/takamii228 ● @int128 ○ GeekFactory : https://int128.hatenablog.com/ ○ SpeakerDeck : https://speakerdeck.com/int128

Slide 50

Slide 50 text

ライセンス ● The Jenkins logo is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. (https://jenkins.io/) ● AWS(Amazon Web Services)は、米国その他の諸国における、 Amazon.com, Inc.またはその関連会社の商標です ● Dockerは、Docker、Inc.の米国およびその他の国における商標または登録商標です ● Mattermostは、Mattermost, Inc. の登録商標です ● SonarQubeは、SonarSource S.A.及びその子会社、関連会社の商標または登録商標です ● Ansibleは、米国およびその他の国において登録された Red Hat, Inc.の商標です ● JIRA、Confluenceは、豪州およびその他の国における Atlassian および/またはその関連会社の登録商標または商標です ● The GitLab logo and wordmark artwork are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.(https://gitlab.com/gitlab-com/gitlab-artwork/blob/master/README.md)