เขียนโค๊ดกับ VS Code บน Docker อย่างไร้รอยต่อด้วย Remote Containers
ปฏิเสธได้ยากว่ายุค 4.0 จะไม่มีใครใช้ Docker และยากยิ่งกว่าที่ความชัง Microsoft จะทำให้รังเกียจ Editor เทพทรูอย่าง Visual Studio Code (VS Code)
แม้ VS Code จะเทพทรู แต่เธอว์ก็ยังเปย์ไม่พอที่จะใช้งานกับ container ได้อย่างไร้รอยต่อ ตัวอย่างสุดคลาสสิคเช่น มีโค้ดอยู่บนเครื่องเรา แต่ติดตั้ง lib ต่าง ๆ ไว้ภายในคอนเทนเนอร์โดยไม่ติดตั้งซ้ำซ้อนบนเครื่องหลัก เหตุเพราะบนเครื่องเราที่ติดตั้ง VS Code ไม่มี lib ดังกล่าว IntelliSense จึงดูเป็นฟังก์ชันพิการ ยิ่งถ้าติดตั้ง extensions บน VS Code สำหรับ lib เวอร์ชันหนึ่ง แต่ในคอนเทนเนอร์กลับใช้ lib คนละเวอร์ชันด้วยแล้ว extensions นั้นยิ่งดูเหมือนพิการซ้ำซ้อนเข้าไปเลยหละ
ถ้าติดตั้ง lib ไว้บนเครื่องหลัก VS Code จะจัดการ IntelliSent ได้อย่างดีเยี่ยม
แต่ถ้า lib มีอยู่เฉพาะใน container VS Code ก็จะดูพิการซ้ำซ้อนนิดหน่อย
หาก IntelliSent มันเป็นปัญหามากนัก ย้าย extensions กับการทำงานพวกนี้ไปไว้ในคอนเทนเนอร์ซะเลยซิ! VS Code จะได้มองเห็นว่ามี lib อะไรอยู่ในคอนเทนเนอร์บ้าง extensions จะได้โชว์ IntelliSent ได้อย่างถูกต้อง
จากแผนภาพ VS Code จะมีสองส่วน ส่วนแรกคือส่วน UI ที่ทำงานอยู่บนเครื่องเรา และส่วน Server ที่เสมือนเป็นปรสิตแอบกระดึ๊บไปฝังตัวอยู่ในคอนเทนเนอร์ Server มองเห็น lib อะไร ส่วน UI ก็จะจัดการแสดงผลออกมาได้อย่างถูกต้อง
เพื่อความสงบของมวลมนุษยชาติ VS Code ตอนนี้ได้สนับสนุนความสามารถของ extension ที่เรียกว่า Visual Studio Code Remote - Containers แล้วหละ
เตรียมความพร้อมก่อนใช้ Remote Containers
หมายเหตุ บทความนี้เขียนขึ้นบนฐานของการใช้งาน Docker บน MacOS ผู้ใช้งานระบบปฏิบัติการอื่น สามารถอ่านเพิ่มเติมได้จากลิงค์ท้ายบทความฮะ
- เรากำลังพูดถึงคอนเทนเนอร์กันอยู่ใช่ไหม ดังนั้นสิ่งแรกที่พลาดไม่ได้คือติดตั้ง Docker
- VS Code ตัวหลักนั้นยังไม่สนับสนุนความสามารถนี้ ต้องติดตั้ง Visual Studio Code Insiders เพื่อทดสอบ
- จะเล่นละครอย่าลืมพระเอก! Remote Development extension คือส่วนสำคัญ ติดตั้งลงไปในตัว Insider (อย่าเผลอไปลงในตัวหลักหละ)
- ถ้าผ่านมาถึงขั้นตอนนี้ได้ แสดงว่าคุณทำบุญมาเยอะ~
รู้จักกับ devcontainer.json
จะให้ VS Code ทำงานกับ container ได้อย่างถูกต้องก็ต้องแนะนำ Dockerfile ให้ VS Code รู้จักซะหน่อย จะได้อัดฉีดตัว server แฝงเร้นไปเป็นปรสิตได้อย่างถูกต้อง นั่นคือหน้าที่สำคัญของไฟล์ devcontainer.json
ที่จะบอกข้อมูลต่าง ๆ ที่ VS Code ต้องการ
เราจะลองส่องโค้ดที่สมบูรณ์แล้วจากการดูดโค้ดด้วยคำสั่ง git clone https://github.com/Microsoft/vscode-remote-try-node
ภายหลังการติดตั้งภายใต้โฟลเดอร์ vscode-remote-try-node จะเจอโฟลเดอร์ย่อยชื่อ .devcontainer
ที่เก็บไฟล์ devcontainer.json
ไว้ หน้าตาประมาณนี้
1{2 "name": "Python Sample",3 "dockerFile": "Dockerfile",4 "appPort": 9000,5 "context": "..",6 "extensions": ["ms-python.python"]7}
เราพบว่าการตั้งค่านี้ชี้ไปที่ Dockerfile ที่เราต้องการใช้งาน
ลำดับถัดไปคือการ build image และเริ่มการทำงานของคอนเทนเนอร์ เริ่มจากกด CMD + Shift + P
หรือ จิ้มพรวดไปที่ไอคอนซ้ายล่างพร้อมเลือกเมนู Remote-Containers: Open Folder in Container
สำคัญมากคืออย่าลืมติดตั้ง Remote Development Extension ก่อนครับ ไม่งั้นเมนูไม่โผล่นะ
ภายหลังการเลือกเมนูดังกล่าว ให้เราเลือกโฟลเดอร์ของโปรเจคที่เราต้องการทำงานด้วย (ก็คือโปรเจคที่ได้มาจาก git clone นั่นหละฮะ) เสร็จสิ้นแล้วรอมัน build image ซักนิดนึง ขั้นตอนนี้ VS Code Server ก็จะแอบมุดรูหนอนเข้าไปในคอนเทนเนอร์ด้วย
ลำดับสุดท้ายคือการ forward port จากคอนเทนเนอร์ให้บนเครื่องเราสามารถเข้าถึงได้ ถ้าแอบดูใน devcontainer.json
จะเห็นว่ามันคือพอร์ต 9000
ตรงส่วนนี้ให้กดไอคอนบนกล่องสีเขียวซ้ายล่างอีกครั้งหรือกด CMD + Shift + P
พร้อมเลือก Remote-Containers: Forward Port from Container...
เลือกทำการ forward จาก 9000 บน container มาที่เครื่อง host ได้เลย หากเลือก Open Browser จากกล่องเมนูที่แสดงล่างขวา ก็จะเปิดเว็บไปที่ http://localhost:9000
พร้อมแสดงข้อความอย่างถูกต้อง
ในส่วนของ packages ต่าง ๆ ที่ใช้ในโค้ดของเราจะถูกติดตั้งที่ /usr/local/lib/python3.7/site-packages/
ภายในคอนเทนเนอร์
เนื่องจาก VS Code Server ได้ถูกติดตั้งในคอนเทนเนอร์แล้ว IntelliSent ก็ควรจะทำงานได้อย่างถูกต้องแม้บนเครื่องหลักของเราจะไม่มีแพคเกจอะไรอยู่เลยก็ตาม ลองเปิดไฟล์ app.py
ทดสอบดูได้ฮะ
Extensions และ Container
Extensions บน VS Code สามารถติดตั้งได้ทั้งแบบ global และบน workspace (บนโปรเจคที่ทำงานอยู่) ในกรณีของการใช้งานบน workspace เราสามารถระบุชื่อ extensions ในไฟล์ .vscode/extensions.json
เพื่อให้ VS Code แจ้งเตือนให้เพื่อน ๆ ในทีมของเราติดตั้ง extensions เหล่านี้สำหรับโปรเจคที่ทำงานร่วมกันได้
การใช้ extensions กับ Remote Container นั้นยิ่งง่ายเข้าไปใหญ่ครับ เพราะเราแค่ระบุ extensions ไว้ในไฟล์ .devcontainer
VS Code ก็จะติดตั้ง extensions เหล่านั้นลงไปในคอนเทนเนอร์ให้อัตโนมัติ
1{2 "name": "Python Sample",3 "dockerFile": "Dockerfile",4 "appPort": 9000,5 "context": "..",6 "extensions": ["ms-python.python"]7}
จากตัวอย่างข้างต้น เราพบว่า extension ชื่อ ms-python.python
จะถูกติดตั้งลงไปในคอนเทนเนอร์อัตโนมัติ ไม่ได้ติดตั้งบนเครื่อง host ของเรา
การใช้งาน Remote Container กับ Docker Compose
Remote Container นั้นสนับสนุนการทำงานกับ Docker Compose เช่นกันครับ เพียงแค่ระบุ dockerComposeFile
เพื่อชี้ตำแหน่งของไฟล์ docker-compose.yml ให้ถูกต้องใน .devcontainer.json
ก็เป็นอันเสร็จสิ้น
ลองดูตัวอย่างการใช้งานดูด้วยโปรเจคที่มีโครงสร้างดังนี้
ไฟล์ devcontainer.json ทำการตั้งค่าของ dockerComposeFile
ดังนี้ครับ
1{2 "name": "Node.js",3 "dockerComposeFile": "../docker-compose.yml",4 "service": "api",5 "workspaceFolder": "/app",6 "shutdownAction": "stopCompose",7 "extensions": ["dbaeumer.vscode-eslint"]8}
โค้ดในไฟล์ index.js ไม่มีอะไรมากใช้งาน express และ lib ที่ชื่อว่า greeting เพื่อสุ่มคำทักทายชาวโลก!
1const express = require('express')2const { random } = require('greeting')34const app = express()56app.get('/', (_, res) => res.send(random()))78app.listen(3000, '0.0.0.0', () => console.log('Listening...'))
แน่นอนว่าเมื่อใช้งาน lib ทั้งสอง ย่อมต้องระบุไว้ใน package.json ด้วยเช่นกัน
1{2 "name": "api",3 "version": "1.0.0",4 "main": "index.js",5 "license": "MIT",6 "scripts": {7 "start": "node src/index.js"8 },9 "dependencies": {10 "express": "^4.16.4",11 "greeting": "^1.0.6"12 }13}
สุดท้ายคือการใช้งาน Docker Compose แบบง่ายด้วยไฟล์ docker-compose.yml
1version: '3'2services:3 api:4 build: ./api5 volumes:6 - ./api:/app/7 - /app/node_modules8 ports:9 - '3000:3000'10 command: yarn start
จากไฟล์ docker-compose.yml ที่ระบุนี้บนเครื่อง host จะไม่มีรายการ packages ต่าง ๆ ในโฟลเดอร์ node_modules
อีกต่อไป แต่จะมีเฉพาะในคอนเทนเนอร์เท่านั้น
ก่อนหน้านี้หากเราปิดกั้นไม่ให้บนเครื่อง host ที่ซึ่ง VS Code สถิตอยู่ มีไลบรารี่ต่าง ๆ ในโฟลเดอร์ node_modules การปิดกั้นนี้จะทำให้ VS Code ไม่รู้จัก lib ต่าง ๆ เป็นเหตุให้ IntelliSent ทำงานได้อย่างไม่สมบูรณ์
ครั้นจะแก้ไขปัญหาข้างต้นด้วยการ sync โฟลเดอร์ node_modules
ระหว่าง host กับคอนเทนเนอร์ก็จะพบปัญหาที่ว่าถ้าไม่ใช่ Linux การ sync ไฟล์ที่มากเกินไปส่งผลต่อความช้าในการทำงาน แม้จะมีโปรเจคอย่าง docker-sync ช่วยแก้ปัญหานี้ความฟินก็ยังอาจสะดุดได้
การมาของ Remote Container นี้ทำให้เราไม่ต้อง sync node_modules
แต่ให้ VS Code Server และ extensions ทำงานกับ node_modules
ในคอนเทนเนอร์แทนนั่นเอง
ทดสอบการทำงานของ Docker Compose และ VS Code Remote Container จะพบว่าสามารถทำงานควบคู่กันได้เช่นกัน
ทดลองเปิดไฟล์ index.js จะพบว่าตอนนี้ VS Code ได้ทำ IntelliSent โดยอิงกับ node_modules
ในคอนเทนเนอร์เป็นที่เรียบร้อยครับ
ข้อจำกัดของ Remote Containers
อย่างแรกที่รู้สึกกระอักกระอ่วนคือการที่ฟีเจอร์นี้ยังใช้กับอิมเมจตระกูล Alpine Linux ไม่ได้นี่หละ ก็ต้องรอดูต่อไปว่าตัว official จะใช้งานได้กับ Alpine ไหม ส่วนข้อจำกัดอื่น ๆ อ่านเพิ่มเติมได้จากเอกสารอ้างอิงเลยครับ
เอกสารอ้างอิง
Developing inside a Container. Retrieved May, 4, 2019, from https://code.visualstudio.com/docs/remote/containers
สารบัญ
- เตรียมความพร้อมก่อนใช้ Remote Containers
- รู้จักกับ devcontainer.json
- Extensions และ Container
- การใช้งาน Remote Container กับ Docker Compose
- ข้อจำกัดของ Remote Containers
- เอกสารอ้างอิง