Series — Flutter CI/CD, ตอน: Setup CI/CD สำหรับเริ่มแรก

Amorn Apichattanakul
6 min readJan 7, 2021

--

guideline นี้คนที่ไม่เคยเขียน Flutter มาก่อน ก็ใช้ได้นะครับ เขียนเป็น step by step เอาไว้

CI/CD คืออะไร?

CI-Continuous Integration

CD-Continuous Delivery

เล่าแบบคร่าวๆนะครับ ก็คือการที่เรามีการ test/deploy แบบ automated นั้นเอง แทนที่จะ run Flutter test, Flutter analyze เองกับมือ เราก็ปล่อยให้ CI/CD ทำให้ แม้กระทั้งการ deploy เราก็ไม่ต้องมานั่ง manual deploy เอา

จำเป็นไหม?

ถ้า Project เล็กๆ ทำ 2–3 เดือนเสร็จแล้วคิดว่าจะไม่กลับมาใช้อีกแล้วก็ไม่ได้จำเป็น แต่เวลาคุณเจอ project ใหญ่ๆ ใช้เวลาเป็นปี มีผู้พัฒนาร่วมกันระดับ 3–4 คน ก็ถือว่าจำเป็น อันนี้ก็เกริ่นแบบคร่าวๆนะครับ ว่าที่มา CI/CD คืออะไร เรามาดูวิธี setup กัน

วิธีการทาง Google ได้ทำไว้ให้แล้วนะครับ สำหรับ Flutter สามารถไปดูที่นี่ได้

แต่ที่ Google ให้มาจะภาพใหญ่ๆ ผมจะมาเจาะภาพรายละเอียดที่ Google ไม่ได้บอกไว้ ซึ่งมันไม่มีหลักตายตัวว่าจะต้องทำตามผมนะครับ แต่เนื่องจากว่าส่วนตัวแล้วเคย set CI/CD สำหรับ iOS ผมก็เลยยังยึดหลักตาม iOS เลยนะครับ

ขั้นตอนแรกเราจะไป setup Ruby Env ซะก่อน วิธี setup สามารถทำได้ตามที่นี่เลย

ทำไมต้องใช้ ruby env?

เนื่องจากว่า automated tools ที่เราจะใช้นั้น based on Ruby ครับ แล้วเราอยากได้ env แยกจาก mac Environment (ผมขอ assume ว่าทุกคนใช้ mac นะครับ เพราะว่าจะใช้ Flutter ได้ สุดท้ายก็ต้องพัฒนา iOS ได้ ก็แปลว่าต้องมี mac กัน)

ruby ที่แถมมากับ mac นั้น เราไม่อยากไปยุ่ง เพราะ core system บางอันก็ยังใช้ ruby version ที่แถมมาอยู่ ถ้าเราไป upgrade อาจจะทำให้ mac ทำงานได้ไม่ปกติ เราก็เลยใช้ rbenv (ruby env) ขึ้นมาแทน

เริ่มจากการ install homebrew ซะก่อน

ด้วย command line นี้เลยครับ

/bin/bash -c “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

หลังจาก install เสร็จแล้ว เราก็จะมาเริ่มลง ruby env กัน ด้วย command line นี้

brew install rbenv

install เสร็จแล้วก็ตามด้วย

rbenv init

พอเสร็จแล้วก็ลองเปิดปิด terminal หรือ restart terminal ก่อนเพื่อความชัวร์ แล้วก็ตามด้วย เพื่อทำการเช็คว่า rbenv install ได้ถูกต้องไหม

curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

ซึ่งควรจะได้ผลคล้ายๆแบบนี้

Checking for `rbenv' in PATH: /usr/local/bin/rbenv
Checking for rbenv shims in PATH: OK
Checking `rbenv install' support: /usr/local/bin/rbenv-install (ruby-build 20170523)
Counting installed Ruby versions: none
There aren't any Ruby versions installed under `~/.rbenv/versions'.
You can install Ruby versions like so: rbenv install 2.2.4
Checking RubyGems settings: OK
Auditing installed plugins: OK

ก็แปลว่า install ได้เรียบร้อยแล้วนะครับ

ต่อไปเริ่ม install Ruby กันได้ หลังจาก ทำ rbenv แล้ว ด้วย command line

rbenv install -l

จะ list ruby ที่สามารถ install ขึ้นมาให้ โดยปกติแล้ว ผมก็เลือก version ใหม่สุดนั้นแหละ :D ชาว geek ต้องใช้ของใหม่สุดเท่านั้น!!!

แล้วก็เลือก version ที่จะ install

rbenv install xxxx

โดยที่ xxxx ก็คือ version ที่ต้องการจะลง ถ้าจะลง version 2.7.1. ก็จะใช้

rbenv install 2.7.1

แล้วก็รอไปครับ นานหน่อย หลังจาก install เสร็จ เราก็มาเช็คกันหน่อยว่ามันลงไปหรือยัง

ruby --version

ก็จะเห็นว่า มี 2 versions แล้วละครับ ตัวของ mac 1 ตัว กับตัวที่ลงใหม่มาอีก 1 ตัว

ให้ set global ruby ไปที่ตัวใหม่แทนด้วยการ

rbenv global xxxx

xxx คือ 2.7.1 ในที่นี้ แล้วแต่คุณใช้ ruby version อะไรที่ install

ตอนนี้ต่อไปมันก็จะใช้ 2.7.1 แทนละครับ เพื่อความชัวร์เราจะเช็ค

ruby --version

อีกครั้งว่ามันไป mark ตรงตัวใหม่หรือยัง แล้วก็

which ruby

จะบอกว่า ruby ที่ install อยู่ที่ไหน ถ้าทำถูกต้อง path จะต้องไปที่

/Users/yourPCName/.rbenv/shims/ruby

ขั้นตอนสุดท้ายละครับไปเปิด bash profile อันนี้แต่ละคนจะไม่เหมือนกันนะครับ บางคนใช้ zshrc หรืออื่นๆ ก็ให้ไปแก้ที่ตามนั้นครับ

open ~/.bash_profile

ใส่อันนี้เข้าไปเลยครับตรงไหนก็ได้

eval "$(rbenv init -)"

เพื่อเป็นการบอกว่าทุกครั้งที่ restart เครื่องนะ เราจะมา set global ruby ให้ตลอดนะ จะได้ไม่ต้องทำเองใหม่ทุกครั้ง

เรียบร้อยครับสำหรับการลง rbenv ต่อมาเราจะลงตัวที่คนส่วนใหญ่ใช้กันก็คือ fastlane

หลังจากที่เราสร้าง project Flutter ด้วย

flutter create my_app

my_app เปลี่ยนชื่อเอาตามชื่อ project นะครับ

แล้วเข้าไปที่ folder ของ ios กับ android เพื่อที่เราจะไป install fastlane สำหรับแต่ละ platform

เราจะข้ามตอน setting up Flutter ไปนะครับ ผมเข้าใจว่าทุกคนมาทำ CI/CD แล้วน่าจะมีพร้อมหมดแล้ว

เข้าไปที่ folder ios ก่อน

cd my_app/ios
gem install fastlane

พอ install เสร็จปุ๊บ ก็ตามด้วย

fastlane init

มันจะถามแบบนี้นะครับ เราเหลือ 3 ไปเลย เพราะยังไง TestFlight กับ App Store distribution ปลายทางก็ที่เดียวกัน

พอคราวนี้มันจะถาม Apple ID ก็ใส่ไปตามจริงเลยครับ เอา Email ที่ได้รับเชิญเป็นผู้พัฒนานะครับ

มันจะถามว่า Account ไหน/ ทีมไหน ก็เลือกไปตามจริงเลยครับ ไม่มีอะไรพิเศษ มันจะ setup เบื้องต้นให้

จนไปถึงขั้นที่มันจะถามว่าจะให้

Do you want fastlane to create the App ID for you on the Apple Developer Portal?

ผมตอบ n ไป ผมมาทำ manual ดีกว่า ซึ่งถ้าตอบ no ไป ต้องมั่นใจแล้วนะครับว่า bundle id เรา ตรงกับ App ID ที่เราจะพัฒนา

ไปดูได้ที่ Xcode app แล้วเลือก General เพื่อดู Bundle Identifier แต่ถ้าจะให้ fastlane ทำให้ก็เลือก yes ไปก็ได้ครับ

พอเสร็จหมดแล้ว เราจะมี Folder fastlane แล้วจะมี Appfile, Fastfile ไว้ให้อยู่

โดยปกติผมจะใช้ fastlane match ด้วย เพื่อทำการ share certificate สำหรับผู้พัฒนาด้วยกัน ถ้าใครปกติไม่ share certificate

============== ก็ข้ามอันนี้ไปได้ครับ ===============

fastlane match init

มันจะถามว่าเราอยากเอา certificate ไว้ที่ไหน อันนี้แล้วแต่ชอบเลยครับ แต่ปกติผมไว้ที่ git เราก็เลือก 1 ไป

มันจะถาม git repo ก็ให้เราไปสร้าง repo ไว้ ที่จะ save share certificate อันนี้นะครับ เช่น

https://gitlab.com/yourRepoName/certificates.git

พอใส่เสร็จตอนนี้มันจะสร้าง Matchfile ให้นะครับ ลองเปิดเข้าไปดู

ซึ่งน่าจะได้ตามด้านล่างนะครับ

ให้เราไป uncomment app_identifier แล้วก็ username ออก

แล้วก็ใส่ bundle identifier ลงไปครับ สมมตคุณต้องไว้ว่า com.bank.myapp ก็ใส่ไปตามนั้น แทนที่ tools.fastlane.app ข้อมูลมันรับเป็น array ได้ เพราะมันจะทำ certificate ให้หลายใบ เราก็ใส่แค่อันเดียว

ส่วน username ก็เป็น email App store ที่ใช้ครับ

ผมกรอกเสร็จหมดแล้ว ก็จะใช้ command

fastlane match development

เพื่อทำ certificate สำหรับ development มันจะถาม password สำหรับ encrypt เอาไว้ จำไว้ให้ดีนะครับ ถ้าลืมก็ต้องทำใหม่ เพราะคนอื่นจะต้องใช้ password นี้ด้วย

มันจะทำการ update download create certificate ให้เรียบร้อยแล้วก็ลง git ที่เรา set ไว้ให้

ขั้นตอนต่อไป development เสร็จแล้ว เราก็ไปของ distribution กันบ้างก็ใช้ตามนี้

fastlane match appstore

ก็จะใช้ file Appstore มาครับ วิธีดูว่าได้มาหรือยังก็ไปที่ Xcode app

เลือก Signing & Capibilities ไปที่ Provisioning Profile ถ้าทำถูกต้องจะต้องมี Match Development xxxxxx ให้เลือก

xxxx คือ bundle ของ app คุณ

ก็เลือกแล้วก็ดูว่าไม่ขึ้นสีแดง แล้วใช้งานได้ก็จบละครับ สำหรับการ share certificate

============================

สำหรับคนที่ไม่ต้องการทำ share certificate ก็ข้ามมาด้านล่างได้เลยครับ

ไปที่ Fastfile ของ folder ios นะครับ เราจะปรับตามนี้ ลอกข้อสอบได้เลยครับ

lane :beta do
build_app(workspace: "Runner.xcworkspace",
scheme: "Runner",
configuration: "Release",
export_options: {
method: "app-store",
provisioningProfiles: {
"xxxxx" => "match AppStore xxxxx",
}
}
)
upload_to_testflight(skip_submission: true, skip_waiting_for_build_processing: true)
end

xxxx คือ bundle identifer ของที่ทำนะครับ แล้วก็แก้ชื่อ ตรงตัวหนาให้ตรงกับข้อมูลแอพที่จะใช้นะครับ

หลักๆก็คือ เราจะใช้ fastlane build app ตาม scheme เหล่านี้ใช้ certificate ตามที่ตั้งไว้

แล้วก็ใช้ upload_to_testflight สำหรับการส่งขึ้น App store นะครับ skip submission กับ skip ทิ้งไว้ให้เป็น true ไว้ เพราะมันจะลดขั้นตอนในการ deploy app ใครอยากลองเป็น false ก็ได้ครับ แต่เวลา app ขึ้น TestFlight แล้ว ต้องไปกด manual release เองนะ

พอ save เสร็จแล้วก็ลองรันเลย

fastlane beta

beta คือชื่อ lane นะครับ ที่อยู่ใน Fastfile เราเปลี่ยนชื่อก็ได้ จะเป็น appstore, release หรืออะไรก็ได้

เรียบร้อยครับของ iOS ก็จะสามารถ deploy ได้แล้วละครับ ถ้าทำถูกหมด

ต่อไปเราก็มาทำอีกครับสำหรับ Android

ไปที่ folder android ที่อยู่ใน project ของ app เรานะครับ แล้วก็ทำ

fastlane init 

เหมือนกันครับเราก็จะได้ตัว fastlane มาเหมือนกันทั้งคู่ละ แต่ว่าของ Android เราจะไม่ได้ทำ fastlane Match นะครับ เพราะว่า Android ไม่ยุ่งยากสำหรับการ dev/deploy เท่า iOS

ส่วนตัวแล้วของ Android ผมจะใช้ Firebase App Distribution

ผมไม่ได้ใช้ alpha/beta ของ Google Play store ครับ เพราะว่ามันยุ่งยากสำหรับ Tester ที่จะต้องไป sign up beta แทนเวลาจะเทส PVT Production มันต้องมา sign out ออกจาก beta tester อีก ผมเลยเลือกใช้ tools ที่ Firebase มีให้

ในที่นี่จะไม่พูดถึงการติดตั้ง Firebase นะครับ เราจะเน้น CI/CD ไปก่อน

สามารถตาม step ของ Firebase ตามนี้ได้เลยครับ

หลังจากติดตั้ง fastlane plugin ของ Firebase App Distribution เสร็จแล้ว

เราจะกลับไปแก้ Fastfile ของ android โดยเราจะเพิ่ม lane beta แบบนี้ ให้แก้ค่าตัวหนา ตามที่ต้องการครับ

lane :beta do
firebase_app_distribution(
app: "appID",
apk_path: "../build/app/outputs/apk/dev/release/app-dev-release.apk",
groups: "bank-tester",
release_notes: "Beta test",
firebase_cli_token: "firebase_token"
)
end

app ให้ไปเอาค่าจาก Firebase มานะครับ ตอนที่เรา login ด้วย Firebase CLI เราจะเห็นค่านี้

groups อันนี้ปกติผมจะแยกกลุ่ม tester ไว้ อันนั้นจะเป็นชื่อเดียวกับกลุ่มใน App Distribution ของ Firebase เลยครับ ที่ Menu Tester & Groups ให้สร้างกลุ่มขึ้นมา

ปกติจะแยกเพราะว่าเวลาเราส่ง build เราจะส่งค่อนข้างบ่อยให้กับ tester ผมก็ให้กลุ่มนี้ได้ทุกคน

ส่วนกลุ่ม UAT ก็จะแยกอีกกลุ่มเพื่อที่จะไม่ต้องรำคาญได้รับ email เยอะๆ

release_notes: จริงๆอยากจะเอา comment ของ git merge ล่าสุดมา แต่ไว้ก่อน ตอนนี้ก็เขียนแบบนี้ไปก่อน

firebase_cli_token ก็เป็นค่าที่ได้มาจาก login ด้วย Firebase CLI ครับ

แต่ก่อนอื่นด้วยความขี้เกียจของผม ก็เลยทำ automated increment buildNumber เอาไว้ด้วย ใครอยาก deploy ก็จะได้ไม่ต้องมาลืมเปลี่ยน Build number

วิธีการก็คือไปที่ fastlane ของ iOS แล้ว add lane ใหม่ครับ ผมใช้ชื่อว่า buildNumber

lane :buildNumber do
lastNum = latest_testflight_build_number + 1
filename = "../../pubspec.yaml"
outdata = File.read(filename).gsub(/version: \d+\.\d+\.\d+\+\d+/, "version: 0.1.0+#{lastNum}")
File.open(filename, 'w') do |out|
out << outdata
end
end

วิธีการก็คือว่า จะไปเอาเลข build ล่าสุดบน testflight แล้วมาบวก 1

พอเสร็จก็จะไปแก้ pubspec.yaml แล้วก็ใช้ reg express หาค่า “version:blahblah” replace ด้วย “version:0.1.0+1 เข้าไปครับ

พอครั้งหน้าทำใหม่ก็จะกลายเป็น +2

ทำไมต้องทำ เพราะว่าเวลาขึ้น TestFlight Apple ไม่อนุญาติให้ใช้เลข build number อันเดิมครับ ส่วน App distribution นั้นยอมได้ เราก็เลยใช้ตัวเลขของ Apple เป็นหลักในการเพิ่มเลขครับ

เรียบร้อยแล้วครับทั้ง iOS/Android ซึ่งการ deploy ผมก็ได้ทำ script bash ไว้แบบนี้

เอา script bash ไปไว้ที่ root ของ project นะครับ ลอกข้อสอบได้เลยครับ สร้าง file ชื่อว่า deploy.sh แล้วเอาข้อมูลนี้ไปลง

#!/bin/bash
set -e
cd ios
fastlane buildNumber
cd ..
flutter clean
flutter build apk --release -t lib/main.dart --target-platform android-arm,android-arm64
cd android
fastlane beta &
cd ..
flutter build ios --release -t lib/main.dart --no-codesign
cd ios
fastlane beta

เรียบร้อยครับผม เวลาเราจะ deploy เราก็รัน command

bash deploy.sh

มันก็จะทำงานตามขั้นตอนด้านบนครับ จะ Clean flutter ก่อนแล้วก็ build Android เสร็จก็จะ upload ขึ้น App distribution ในขณะที่ upload นั้น ก็จะเริ่ม build ของ iOS ด้วยเลยครับ

ผมเอา Android ขึ้นก่อน เพราะว่า Android มัน build ง่ายและเร็วกว่าจะได้ทำคู่ขนานกันไป

จริงๆมันมีส่วนที่มากกว่านี้อีกนิดนะครับ แต่ว่าผมเอาออกไป ก็คือเวลา เรา deploy เสร็จ ผมจะให้มันทำ git tag + move ticket JIRA + Slack + line bot ครับ

เพื่อแจ้งเตือนให้รู้ว่าเรา deploy เสร็จแล้วนะ รอ build ได้เลย

ทั้งหมดก็เรียบร้อยแล้วครับผม สำหรับการทำ basic CI/CD ในเครื่องตัวเอง ต่อไปใครๆก็ deploy ได้แล้ว เพียง run command bash เท่านั้นเอง แต่อาจจะต้อง setup พวก certificate กับ Firebase CLI ก่อนนะครับ ซึ่งในอนาคตผมจะเอา CI/CD ที่ต่อกับ Cloud ทำเป็น Pipeline ไว้ให้ ก็ปกติจะใช้ตัว Gitlab Runner อยู่ครับ เพราะว่าสมัยที่ทำ CI/CD ของ Flutter แรกๆ ไม่มีเจ้าไหน support เลย ก็เลยเอาเครื่องตัวเองนี่แหละ ลง Gitlab Runner แล้วก็ fetch จาก gitlab มา

ไว้รอบหน้าจะมาพูดถึงการแบ่ง flavor ของ Flutter กันนะครับ เพื่อที่จะแบ่ง env ต่างๆ ไม่ให้ปนกัน การทำ flavor ก็จะทำให้เราสามารถ set dev env, prod env แยกออกจากกันได้ โดยใช้ code ชุดเดียวกัน

--

--

Amorn Apichattanakul
Amorn Apichattanakul

Written by Amorn Apichattanakul

Google Developer Expert for Flutter & Dart | Senior Flutter/iOS Software Engineer @ KBTG Join us for more info https://www.facebook.com/groups/1565880160729817

No responses yet