Series — Flutter CI/CD, ตอน: Setup CI/CD สำหรับเริ่มแรก
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 ชุดเดียวกัน