การปรับใช้ Laravel Zero Downtime
เผยแพร่แล้ว: 2022-03-11เมื่อพูดถึงการอัปเดตแอปพลิเคชันสด มีสองวิธีในการดำเนินการนี้โดยพื้นฐาน
ในแนวทางแรก เราทำการเปลี่ยนแปลงสถานะระบบของเราทีละส่วน ตัวอย่างเช่น เราอัปเดตไฟล์ แก้ไขคุณสมบัติของสภาพแวดล้อม ติดตั้งสิ่งจำเป็นเพิ่มเติม และอื่นๆ ในแนวทางที่สอง เราทำลายเครื่องจักรทั้งหมดและสร้างระบบใหม่ด้วยอิมเมจใหม่และการกำหนดค่าที่เปิดเผย (เช่น การใช้ Kubernetes)
การปรับใช้ Laravel ทำได้ง่าย
บทความนี้ครอบคลุมถึงแอปพลิเคชันที่มีขนาดค่อนข้างเล็กเป็นหลัก ซึ่งอาจไม่ได้โฮสต์อยู่ในระบบคลาวด์ แม้ว่าฉันจะพูดถึงว่า Kubernetes สามารถช่วยเราได้มากในการปรับใช้นอกเหนือจากสถานการณ์ "ไม่มีระบบคลาวด์" ได้อย่างไร นอกจากนี้เรายังจะหารือเกี่ยวกับปัญหาทั่วไปและเคล็ดลับสำหรับการดำเนินการอัปเดตที่ประสบความสำเร็จซึ่งอาจใช้ได้ในสถานการณ์ต่างๆ ไม่ใช่แค่กับการปรับใช้ Laravel เท่านั้น
สำหรับจุดประสงค์ของการสาธิตนี้ ฉันจะใช้ตัวอย่าง Laravel แต่โปรดจำไว้ว่าแอปพลิเคชัน PHP ใดๆ สามารถใช้แนวทางที่คล้ายกันได้
การกำหนดเวอร์ชัน
สำหรับผู้เริ่มต้น เป็นสิ่งสำคัญที่เราจะต้องทราบเวอร์ชันของโค้ดที่ใช้งานจริงในปัจจุบัน สามารถรวมไว้ในไฟล์บางไฟล์หรืออย่างน้อยก็ในชื่อโฟลเดอร์หรือไฟล์ สำหรับการตั้งชื่อ หากเราปฏิบัติตามแนวทางปฏิบัติมาตรฐานของการกำหนดเวอร์ชันเชิงความหมาย เราสามารถใส่ข้อมูลในนั้นได้มากกว่าเพียงแค่ตัวเลขเดียว
เมื่อพิจารณาจากรุ่นที่แตกต่างกันสองรายการ ข้อมูลที่เพิ่มเข้ามานี้สามารถช่วยให้เราเข้าใจธรรมชาติของการเปลี่ยนแปลงที่เกิดขึ้นระหว่างกันได้อย่างง่ายดาย
การกำหนดเวอร์ชันของรีลีสเริ่มต้นด้วยระบบควบคุมเวอร์ชัน เช่น Git สมมติว่าเราได้เตรียมรุ่นสำหรับการปรับใช้ เช่น เวอร์ชัน 1.0.3 เมื่อพูดถึงการจัดระเบียบรีลีสและโฟลว์โค้ดเหล่านี้ มีรูปแบบการพัฒนาที่แตกต่างกัน เช่น การพัฒนาแบบลำต้นและโฟลว์ Git ซึ่งคุณสามารถเลือกหรือผสมผสานตามความชอบของทีมและลักษณะเฉพาะของโปรเจ็กต์ของคุณ ในท้ายที่สุด เรามักจะลงเอยด้วยการเปิดตัวของเราที่ติดแท็กตามสาขาหลักของเรา
หลังจากการคอมมิต เราสามารถสร้างแท็กง่ายๆ ได้ดังนี้:
git tag v1.0.3
จากนั้นเรารวมแท็กในขณะที่รันคำสั่งพุช:
git push <origin> <branch> --tags
เรายังเพิ่มแท็กให้กับคอมมิตเก่าได้โดยใช้แฮช
การรับไฟล์เผยแพร่ไปยังปลายทางของพวกเขา
การปรับใช้ Laravel ต้องใช้เวลา แม้ว่าจะเป็นเพียงการคัดลอกไฟล์ก็ตาม อย่างไรก็ตาม แม้ว่าจะใช้เวลาไม่นานเกินไป เป้าหมายของเราคือการทำให้ เวลาหยุดทำงานเป็นศูนย์
ดังนั้น เราควรหลีกเลี่ยงการติดตั้งการอัพเดทแบบแทนที่ และไม่ควรเปลี่ยนไฟล์ที่กำลังให้บริการอยู่ เราควรปรับใช้กับไดเร็กทอรีอื่นและทำสวิตช์เมื่อการติดตั้งพร้อมสมบูรณ์เท่านั้น
จริงๆ แล้ว มีเครื่องมือและบริการต่างๆ มากมายที่ช่วยเราในการ Deploy ได้ เช่น Envoyer.io (โดย Jack McDade ดีไซเนอร์ Laravel.com), Capistrano, Deployer เป็นต้น ฉันยังไม่ได้ใช้งานทั้งหมดในการผลิต เลยทำไม่ได้ ให้คำแนะนำหรือเขียนการเปรียบเทียบที่ครอบคลุม แต่ให้ฉันแสดงแนวคิดเบื้องหลังผลิตภัณฑ์เหล่านี้ หากบางส่วน (หรือทั้งหมด) ไม่ตรงกับความต้องการของคุณ คุณสามารถสร้างสคริปต์ที่กำหนดเองได้เสมอเพื่อทำให้กระบวนการเป็นไปโดยอัตโนมัติในวิธีที่ดีที่สุดที่คุณเห็นว่าเหมาะสม
สำหรับวัตถุประสงค์ของการสาธิตนี้ สมมติว่าแอปพลิเคชัน Laravel ของเราให้บริการโดยเซิร์ฟเวอร์ Nginx จากเส้นทางต่อไปนี้:
/var/www/demo/public
อันดับแรก เราจำเป็นต้องมีไดเร็กทอรีเพื่อวางไฟล์เผยแพร่ทุกครั้งที่เราทำการปรับใช้ นอกจากนี้ เราต้องการ symlink ซึ่งจะชี้ไปที่รุ่นการทำงานปัจจุบัน ในกรณีนี้ /var/www/demo
จะทำหน้าที่เป็นลิงก์เชื่อมโยงของเรา การกำหนดตัวชี้ใหม่จะทำให้เราสามารถเปลี่ยนแปลงการเปิดตัวได้อย่างรวดเร็ว
ในกรณีที่เรากำลังติดต่อกับเซิร์ฟเวอร์ Apache เราอาจจำเป็นต้องอนุญาตลิงก์เชื่อมโยงในการกำหนดค่า:
Options +FollowSymLinks
โครงสร้างของเราอาจเป็นดังนี้:
/opt/demo/release/v0.1.0 /opt/demo/release/v0.1.1 /opt/demo/release/v0.1.2
อาจมีไฟล์บางไฟล์ที่เราจำเป็นต้องคงอยู่ผ่านการปรับใช้ที่แตกต่างกัน เช่น ไฟล์บันทึก (ถ้าเราไม่ได้ใช้ Logstash อย่างเห็นได้ชัด) ในกรณีของการปรับใช้ Laravel เราอาจต้องการเก็บไดเร็กทอรีการจัดเก็บและไฟล์การกำหนดค่า .env เราสามารถแยกไฟล์เหล่านี้ออกจากไฟล์อื่นและใช้ symlink แทนได้
ในการดึงไฟล์รีลีสของเราจากที่เก็บ Git เราสามารถใช้คำสั่งโคลนหรือคำสั่งเก็บถาวร บางคนใช้ git clone แต่คุณไม่สามารถโคลนคอมมิตหรือแท็กเฉพาะได้ ซึ่งหมายความว่าดึงข้อมูลที่เก็บทั้งหมดแล้วจึงเลือกแท็กเฉพาะ เมื่อที่เก็บมีหลายสาขาหรือมีประวัติขนาดใหญ่ ขนาดของที่เก็บจะใหญ่กว่าไฟล์เก็บถาวรรีลีสอย่างมาก ดังนั้น หากคุณไม่ต้องการ git repo ในการผลิตโดยเฉพาะ คุณสามารถใช้ git archive
ซึ่งช่วยให้เราดึงเฉพาะไฟล์ที่เก็บถาวรโดยใช้แท็กเฉพาะ ข้อดีอีกประการของการใช้อย่างหลังคือเราสามารถละเลยไฟล์และโฟลเดอร์บางไฟล์ที่ไม่ควรปรากฏในสภาพแวดล้อมการใช้งานจริง เช่น การทดสอบ สำหรับสิ่งนี้ เราเพียงแค่ต้องตั้งค่าคุณสมบัติการ export-ignore ใน .gitattributes file
ในรายการตรวจสอบ OWASP Secure Coding Practices คุณจะพบคำแนะนำต่อไปนี้: “ลบรหัสทดสอบหรือฟังก์ชันใดๆ ที่ไม่ได้มีไว้สำหรับการใช้งานจริง ก่อนปรับใช้”
หากเรากำลังดึงข้อมูลรีลีสจากระบบควบคุมเวอร์ชันต้นทาง git archive และ export-ignore สามารถช่วยเราได้ตามข้อกำหนดนี้
มาดูสคริปต์แบบง่าย (มันจะต้องมีการจัดการข้อผิดพลาดที่ดีกว่าในการผลิต):
ปรับใช้.sh
#!/bin/bash # Terminate execution if any command fails set -e # Get tag from a script argument TAG=$1 GIT_REMOTE_URL='here should be a remote url of the repo' BASE_DIR=/opt/demo # Create folder structure for releases if necessary RELEASE_DIR=$BASE_DIR/releases/$TAG mkdir -p $RELEASE_DIR mkdir -p $BASE_DIR/storage cd $RELEASE_DIR # Fetch the release files from git as a tar archive and unzip git archive \ --remote=$GIT_REMOTE_URL \ --format=tar \ $TAG \ | tar xf - # Install laravel dependencies with composer composer install -o --no-interaction --no-dev # Create symlinks to `storage` and `.env` ln -sf $BASE_DIR/.env ./ rm -rf storage && ln -sf $BASE_DIR/storage ./ # Run database migrations php artisan migrate --no-interaction --force # Run optimization commands for laravel php artisan optimize php artisan cache:clear php artisan route:cache php artisan view:clear php artisan config:cache # Remove existing directory or symlink for the release and create a new one. NGINX_DIR=/var/www/public mkdir -p $NGINX_DIR rm -f $NGINX_DIR/demo ln -sf $RELEASE_DIR $NGINX_DIR/demo
สำหรับการปรับใช้รุ่นของเรา เราสามารถดำเนินการดังต่อไปนี้:

deploy.sh v1.0.3
หมายเหตุ: ในตัวอย่างนี้ v1.0.3 เป็นแท็ก git ของรุ่นของเรา
นักแต่งเพลงเกี่ยวกับการผลิต?
คุณอาจสังเกตเห็นว่าสคริปต์กำลังเรียกใช้ Composer เพื่อติดตั้งการพึ่งพา แม้ว่าคุณจะเห็นสิ่งนี้ในบทความมากมาย แต่ก็อาจมีปัญหากับแนวทางนี้ โดยทั่วไป แนวทางปฏิบัติที่ดีที่สุดคือการสร้างบิวด์แอปพลิเคชันที่สมบูรณ์ และพัฒนาบิลด์นี้ผ่านสภาพแวดล้อมการทดสอบต่างๆ ของโครงสร้างพื้นฐานของคุณ ในท้ายที่สุด คุณจะมีบิลด์ที่ทดสอบอย่างละเอียด ซึ่งสามารถนำไปใช้กับการใช้งานจริงได้อย่างปลอดภัย แม้ว่าบิลด์ทุกอันควรทำซ้ำได้ตั้งแต่เริ่มต้น แต่ก็ไม่ได้หมายความว่าเราควรสร้างแอปขึ้นใหม่ในขั้นตอนต่างๆ เมื่อเราทำการติดตั้งผู้แต่งในเวอร์ชันที่ใช้งานจริง นี่ไม่ใช่บิลด์เดียวกับรุ่นทดสอบอย่างแท้จริง และนี่คือสิ่งที่อาจผิดพลาดได้:
- ข้อผิดพลาดของเครือข่ายสามารถขัดจังหวะการดาวน์โหลดการขึ้นต่อกัน
- ผู้จำหน่ายห้องสมุดอาจไม่ติดตาม SemVer เสมอไป
ข้อผิดพลาดของเครือข่ายสามารถสังเกตได้ง่าย สคริปต์ของเราจะหยุดทำงานแม้เกิดข้อผิดพลาด แต่การเปลี่ยนแปลงที่แตกหักในไลบรารีอาจเป็นเรื่องยากมากที่จะปักหมุดโดยไม่ต้องทำการทดสอบ ซึ่งคุณไม่สามารถทำได้ในเวอร์ชันที่ใช้งานจริง ขณะติดตั้งการพึ่งพา Composer, npm และเครื่องมืออื่นๆ ที่คล้ายคลึงกันจะขึ้นอยู่กับการกำหนดเวอร์ชันเชิงความหมาย–major.minor.patch หากคุณเห็น ~1.0.2 ใน composer.json แสดงว่าต้องติดตั้งเวอร์ชัน 1.0.2 หรือเวอร์ชันแพตช์ล่าสุด เช่น 1.0.4 หากคุณเห็น ^1.0.2 หมายถึงการติดตั้งเวอร์ชัน 1.0.2 หรือเวอร์ชันรองหรือแพตช์ล่าสุด เช่น 1.1.0 เราเชื่อมั่นว่าผู้จำหน่ายห้องสมุดจะชนกับตัวเลขหลักเมื่อมีการแนะนำการเปลี่ยนแปลงการแตกหัก แต่บางครั้งข้อกำหนดนี้พลาดหรือไม่ปฏิบัติตาม มีกรณีดังกล่าวในอดีต แม้ว่าคุณจะใส่เวอร์ชันคงที่ใน composer.json ของคุณ การขึ้นต่อกันของคุณอาจมี ~ และ ^ ใน composer.json
ในความคิดของฉัน หากสามารถเข้าถึงได้ วิธีที่ดีกว่าคือการใช้ที่เก็บสิ่งประดิษฐ์ (Nexus, JFrog เป็นต้น) บิลด์รุ่นที่มีการพึ่งพาที่จำเป็นทั้งหมดจะถูกสร้างขึ้นครั้งเดียวในขั้นต้น สิ่งประดิษฐ์นี้จะถูกเก็บไว้ในที่เก็บและดึงข้อมูลสำหรับขั้นตอนการทดสอบต่างๆ จากที่นั่น นอกจากนี้ยังเป็นบิลด์ที่จะนำไปใช้กับการใช้งานจริง แทนที่จะสร้างแอปขึ้นมาใหม่จาก Git
รักษารหัสและฐานข้อมูลเข้ากันได้
เหตุผลที่ฉันตกหลุมรัก Laravel ตั้งแต่แรกเห็นก็คือการที่ผู้เขียนให้ความสนใจในรายละเอียดอย่างใกล้ชิด คิดถึงความสะดวกของนักพัฒนา และยังรวมเอาแนวปฏิบัติที่ดีที่สุดมากมายไว้ในเฟรมเวิร์ก เช่น การโยกย้ายฐานข้อมูล
การย้ายฐานข้อมูลทำให้เราสามารถซิงค์ฐานข้อมูลและรหัสของเราได้ การเปลี่ยนแปลงทั้งสองสามารถรวมอยู่ในการคอมมิตครั้งเดียว ดังนั้นจึงเป็นการเปิดตัวครั้งเดียว อย่างไรก็ตาม นี่ไม่ได้หมายความว่าการเปลี่ยนแปลงใดๆ สามารถทำให้ใช้งานได้โดยไม่ต้องหยุดทำงาน ในบางจุดระหว่างการปรับใช้ จะมีเวอร์ชันต่างๆ ของแอปพลิเคชันและฐานข้อมูลที่ทำงานอยู่ ในกรณีที่มีปัญหา จุดนี้อาจจะเปลี่ยนเป็นช่วงเวลาก็ได้ เราควรพยายามทำให้ทั้งคู่เข้ากันได้กับเวอร์ชันก่อนหน้าของคู่หูของพวกเขาเสมอ: ฐานข้อมูลเก่า - แอพใหม่, ฐานข้อมูลใหม่ - แอพเก่า
ตัวอย่างเช่น สมมติว่าเรามีคอลัมน์ address
และจำเป็นต้องแยกออกเป็น address1
และ address2
เพื่อให้ทุกอย่างเข้ากันได้ เราอาจต้องมีรุ่นหลายรุ่น
- เพิ่มสองคอลัมน์ใหม่ในฐานข้อมูล
- แก้ไขแอปพลิเคชันเพื่อใช้ฟิลด์ใหม่ทุกครั้งที่ทำได้
- ย้ายข้อมูล
address
ไปยังคอลัมน์ใหม่แล้ววาง
กรณีนี้เป็นตัวอย่างที่ดีว่าการเปลี่ยนแปลงเล็กน้อยจะดียิ่งขึ้นสำหรับการปรับใช้อย่างไร การย้อนกลับยังง่ายกว่า หากเรากำลังเปลี่ยนฐานรหัสและฐานข้อมูลเป็นเวลาหลายสัปดาห์หรือหลายเดือน การอัปเดตระบบที่ใช้งานจริงโดยไม่มีการหยุดทำงานอาจเป็นไปไม่ได้
ความสุดยอดของ Kubernetes
แม้ว่าขนาดของแอปพลิเคชันของเราอาจไม่ต้องการระบบคลาวด์ โหนด และ Kubernetes ฉันยังคงต้องการพูดถึงลักษณะการใช้งานใน K8 ในกรณีนี้ เราไม่ทำการเปลี่ยนแปลงระบบ แต่ประกาศสิ่งที่เราต้องการบรรลุและสิ่งที่ควรทำในการจำลองจำนวน จากนั้น Kubernetes จะตรวจสอบให้แน่ใจว่าสถานะจริงตรงกับสถานะที่ต้องการ
เมื่อใดก็ตามที่เรามีรีลีสใหม่พร้อม เราจะสร้างอิมเมจที่มีไฟล์ใหม่อยู่ในนั้น แท็กรูปภาพด้วยเวอร์ชันใหม่ และส่งไปยัง K8 หลังจะหมุนภาพของเราภายในคลัสเตอร์อย่างรวดเร็ว มันจะรอก่อนที่แอปพลิเคชันจะพร้อมตามการตรวจสอบความพร้อมที่เราจัดเตรียมไว้ จากนั้นจึงเปลี่ยนเส้นทางการรับส่งข้อมูลไปยังแอปพลิเคชันใหม่โดยไม่ได้สังเกตและฆ่าแอปพลิเคชันเก่า เราสามารถเรียกใช้แอปของเราได้หลายเวอร์ชันอย่างง่ายดาย ซึ่งจะทำให้เราสามารถปรับใช้สีน้ำเงิน/เขียว หรือ Canary ได้ด้วยคำสั่งเพียงไม่กี่คำสั่ง
หากคุณสนใจ จะมีการสาธิตที่น่าประทับใจในการพูดคุยเรื่อง “9 ขั้นตอนสู่ความยอดเยี่ยมด้วย Kubernetes โดย Burr Sutter”