云vscode搭建之使用容器化部署的方法
Vscode作為一個(gè)輕量級(jí)的IDE,其支持豐富的插件,而通過這些插件我們就可以實(shí)現(xiàn)在Vscode中寫任何語(yǔ)言的代碼。Code-Server是Vscode的網(wǎng)頁(yè)版,啟動(dòng)Code-Server之后我們就可以在瀏覽器中打開vscode來愉快的編寫代碼了。這種方式非常適合我們做linux編程。使用C/C++的時(shí)候,在windows上編寫的代碼在linux上可能跑不了,而且安裝linux圖形界面,然后在圖像界面中使用vscode又很麻煩。當(dāng)然也可以使用vscode的遠(yuǎn)程開發(fā)。但是我認(rèn)為啟動(dòng)code-server來在瀏覽器上使用vscode也是非常方便的。
隨著容器化的發(fā)展,現(xiàn)在涌現(xiàn)出了很多云IDE,比如騰訊的Cloud Studio,但是其也是基于Code-Server進(jìn)行開發(fā)部署的,用了它的云IDE后,我便產(chǎn)生出了自己部署一個(gè)這樣的云IDE的想法。
1、Code-Server下載部署
1.1 Code-Server下載
下載地址:https://github.com/coder/code-server/releases/

在上面的網(wǎng)址中下載code-server,并將其傳輸?shù)絣inux服務(wù)器上。
也可以在linux服務(wù)器中直接使用命令來下載:
wget https://github.com/coder/code-server/releases/download/v4.6.1/code-server-4.6.1-linux-amd64.tar.gz
1.2 Code-Server部署
1.解壓tar.gz文件:
tar -zxvf code-server-4.6.1-linux-amd64.tar.gz
2.進(jìn)入code-server目錄:
cd code-server-4.6.1-linux-amd64
3.設(shè)置密碼到環(huán)境變量中
export PASSWORD="xxxx"
4.啟動(dòng)code-server
./code-server --port 8888 --host 0.0.0.0 --auth password
在瀏覽器中輸入ip:8888來訪問如下:

后臺(tái)運(yùn)行的方式:
nohup ./code-server --port 8888 --host 0.0.0.0 --auth password &
1.3 Docker部署Code-Server
接下來將介紹使用Docker的方式來部署Code-Server:
下面的Dockerfile創(chuàng)建了一個(gè)帶有Golang開發(fā)環(huán)境的容器,然后在容器中運(yùn)行Code-Server,將Dockerfile放在跟code-server-4.4.0-linux-amd64.tar.gz同目錄。
1.創(chuàng)建名為Dockerfile的文件(Dockerfile要跟code-server的tar.gz文件在同一目錄中),將下面內(nèi)容復(fù)制進(jìn)去
FROM golang WORKDIR /workspace RUN cp /usr/local/go/bin/* /usr/local/bin COPY code-server-4.4.0-linux-amd64.tar.gz . RUN tar zxvf code-server-4.4.0-linux-amd64.tar.gz ENV GO111MODULE on ENV GOPROXY https://goproxy.cn ENV PASSWORD abc123 WORKDIR /workspace/code-server-4.4.0-linux-amd64 EXPOSE 9999 CMD ["./code-server", "--port", "9999", "--host", "0.0.0.0", "--auth", "password"]
2.然后執(zhí)行命令構(gòu)建Docker鏡像:
docker build -t code-server .
3.運(yùn)行容器
docker run -d --name code-server -p 9999:9999 code-server
2. 一個(gè)小問題
下面的內(nèi)容針對(duì)Docker部署的Code-Server。
想象這樣一個(gè)場(chǎng)景,我們開發(fā)了一個(gè)類似Cloud Studio的云IDE,每啟動(dòng)一個(gè)工作空間我們就通過Docker或者Kubernetes來創(chuàng)建一個(gè)容器,然后在容器中部署一個(gè)Code-Server,最后通過將端口暴露出去給用戶使用。
云IDE使用起來很方便,打開和銷毀的很迅速,即開即用。用戶使用Golang在云IDE中寫了一個(gè)http服務(wù)器,想要在他電腦的瀏覽器上訪問,卻發(fā)現(xiàn)訪問不了。那么為什么Code-Server的端口就可以訪問,其它端口無法訪問呢。因?yàn)槲覀冊(cè)趩?dòng)容器的時(shí)候就已經(jīng)預(yù)想到要訪問這個(gè)端口,然后將端口暴露出去了,也就是建立了端口映射。在容器中新啟動(dòng)的端口并沒有建立映射,因此只能在服務(wù)器內(nèi)部訪問,而不能在用戶電腦上訪問。
那么如何讓用戶也可以訪問到呢,我們可以在主機(jī)上部署一個(gè)代理服務(wù)器,用戶訪問這個(gè)代理服務(wù)器,然后轉(zhuǎn)發(fā)請(qǐng)求到容器中,再將響應(yīng)轉(zhuǎn)發(fā)給用戶。
那么如何發(fā)現(xiàn)用戶啟動(dòng)服務(wù)器監(jiān)聽了哪個(gè)端口呢,首先我能想到的就是啟動(dòng)一個(gè)程序每隔一段時(shí)間查詢一下是否有新的端口被監(jiān)聽。獲取端口信息可以使用netstat命令或者lsof命令,在此我選擇了netstat,就有了下面的一個(gè)程序:
2.1 端口監(jiān)聽
這個(gè)程序會(huì)每隔一秒獲取一下有哪些端口處于LISTEN狀態(tài),然后對(duì)比上一次的狀態(tài),看是否有新的端口被監(jiān)聽。當(dāng)我們監(jiān)聽了新的80端口后,就會(huì)輸出:Find new port: 80
package main
import (
"bytes"
"fmt"
"os/exec"
"strconv"
"time"
)
func main() {
listener := NewPortListener()
pc := listener.GetPortChan()
go listener.FindNewPortLoop()
for {
port := <-pc
fmt.Println("Find new port:", port)
}
}
type PortListener struct {
portChan chan uint16
}
func NewPortListener() *PortListener {
return &PortListener{
portChan: make(chan uint16, 1),
}
}
func (p *PortListener) GetPortChan() <-chan uint16 {
return p.portChan
}
func (p *PortListener) FindNewPortLoop() {
ports := p.getListeningPorts() // 程序啟動(dòng)后先獲取一次處于Listen狀態(tài)的端口
set := map[uint16]struct{}{}
for _, port := range ports {
set[port] = struct{}{}
}
for { // 然后每隔一秒獲取一次,并與前一次的信息進(jìn)行對(duì)比,查找是否啟動(dòng)了新的端口
tmpSet := map[uint16]struct{}{}
ports = p.getListeningPorts()
for _, port := range ports {
if _, ok := set[port]; !ok {
p.portChan <- port
}
tmpSet[port] = struct{}{}
}
set = tmpSet
time.Sleep(time.Second * 3)
}
}
func (p *PortListener) getListeningPorts() []uint16 {
cmd := exec.Command("netstat", "-ntlp") // 運(yùn)行netstat命令獲取處于Listen狀態(tài)的端口信息
res, err := cmd.CombinedOutput() // 獲取結(jié)果
fmt.Println(string(res))
if err != nil {
fmt.Println("Execute netstat failed")
return nil
}
return p.parsePort(res) // 對(duì)結(jié)果進(jìn)行解析
}
func (p *PortListener) parsePort(msg []byte) []uint16 { // 解析出處于LISTEN狀態(tài)的端口,只要端口號(hào)
idx := bytes.Index(msg, []byte("tcp"))
colums := bytes.Split(msg[idx:], []byte("\n"))
res := make([]uint16, 0, len(colums)-1)
for i := 0; i < len(colums)-1; i++ {
item := p.findThirdItem(colums[i])
if item != nil {
m := bytes.IndexByte(item, ':') + 1
for item[m] == ':' {
m++
}
p, err := strconv.Atoi(string(item[m:]))
if err == nil {
res = append(res, uint16(p))
} else {
fmt.Println(err)
}
}
}
return res
}
func (p *PortListener) findThirdItem(colum []byte) []byte {
count := 0
for i := 0; i < len(colum); {
if colum[i] == ' ' {
for colum[i] == ' ' {
i++
}
count++
continue
}
if count == 3 {
start := i
for colum[i] != ' ' {
i++
}
return colum[start:i]
}
i++
}
return nil
}
2.2 使用VS-Code插件
但是上面的程序也無法通知到用戶,在使用Cloud Studio的時(shí)候,啟動(dòng)了新的端口,這個(gè)云IDE就會(huì)提醒發(fā)現(xiàn)了新的端口,是否要在瀏覽器中訪問。因此我就想到了實(shí)現(xiàn)這樣一個(gè)插件,因此下面部分就是實(shí)現(xiàn)一個(gè)vscode的插件來發(fā)現(xiàn)是否有新的端口被監(jiān)聽了,然后提醒用戶是否在瀏覽器中訪問。
下面只是簡(jiǎn)單介紹,想要了解vscode插件的詳細(xì)開發(fā)過程的自行搜索。
1.首先安裝yeoman腳手架工具,以及官方提供的腳手架工具:
npm install -g yo generator-code
2.創(chuàng)建項(xiàng)目,選擇要?jiǎng)?chuàng)建的項(xiàng)目以及其它信息
yo code
3.創(chuàng)建完成后,就可以編寫插件了
// extension.js
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
/**
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand('port-finder.helloWorld', function () {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World from port_finder!');
});
context.subscriptions.push(disposable);
initGetPorts()
}
var s = new Set()
function initGetPorts() {
getListeningPorts(function(ports) {
ports.forEach(p => {
s.add(p)
})
setInterval(function() { // 設(shè)置定時(shí)器,每隔一秒獲取一次
listenPortChange()
}, 1000)
})
}
function listenPortChange() {
// 獲取處于LISTEN狀態(tài)的端口
getListeningPorts(function(ports) {
var tmpSet = new Set()
ports.forEach(p => {
if (!s.has(p)) {
// 發(fā)現(xiàn)新的端口被監(jiān)聽就提醒用戶是否在瀏覽器中打開
vscode.window.showInformationMessage("發(fā)現(xiàn)新開啟的端口:" + p + ",是否在瀏覽器中訪問?", "是", "否", "不再提示")
.then(result=> {
if (result === "是") {
// 在瀏覽器中打開來訪問代理服務(wù)器,后面帶上端口信息,以便代理服務(wù)器知道訪問容器的哪個(gè)端口
vscode.env.openExternal(vscode.Uri.parse(`http://192.168.44.100/proxy/` + p))
}
})
}
tmpSet.add(p)
})
s = tmpSet
})
}
function getListeningPorts(callback) {
var exec = require('child_process').exec;
exec('netstat -nlt', function(error, stdout, stderr){
if(error) {
console.error('error: ' + error);
return;
}
var ports = parsePort(stdout)
callback(ports)
})
}
function parsePort(msg) {
var idx = msg.indexOf("tcp")
msg = msg.slice(idx, msg.length)
var colums = msg.split("\n")
var ret = new Array()
colums = colums.slice(0, colums.length - 1)
colums.forEach(element => {
var port = findPort(element)
if (port != -1) {
ret.push(port)
}
});
return ret;
}
function findPort(colum) {
var idx = colum.indexOf(':')
var first = colum.slice(0, idx)
while (colum[idx] == ':') {
idx++
}
var second = colum.slice(idx, colum.length)
var fidx = first.lastIndexOf(' ')
var sidx = second.indexOf(' ')
var ip = first.slice(fidx + 1, first.length)
var port = second.slice(0, sidx)
if (ip == "127.0.0.1") {
return -1
} else {
return Number(port)
}
}
// this method is called when your extension is deactivated
function deactivate() {}
module.exports = {
activate,
deactivate
}
4.然后構(gòu)建項(xiàng)目,首先安裝vsce庫(kù),再打包
npm i -g vsce vsce package
5.打包后生成了vsix文件,將vsix文件上傳到服務(wù)器,然后再拷貝到docker容器中
# docker拷貝命令 docker cp 主機(jī)文件名 容器ID或容器名:/容器內(nèi)路徑
然后在瀏覽器中的vscode中選擇vsix文件來安裝插件

安裝完之后,我們的插件在vscode打開后就會(huì)啟動(dòng),然后每隔一秒查詢一個(gè)端口情況。
測(cè)試
接下來,測(cè)試一下插件:
在vscode中寫了一個(gè)http服務(wù)器,然后啟動(dòng)這個(gè)服務(wù)器,看插件是否能發(fā)現(xiàn)這個(gè)端口被監(jiān)聽了
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type KK struct {
Name string `json:"name"`
Prictice_time string `json:"prictice time"`
Hobby string `json:"hobby"`
}
func main() {
engine := gin.Default()
engine.GET("/", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, &KK{
Name: "kunkun",
Prictice_time: "two and a half years",
Hobby: "sing jump and rap",
})
})
engine.Run(":8080")
}運(yùn)行http服務(wù)器:
go run main.go
可以看到,它彈出了提示,提示我們是否在瀏覽器中打開

但是現(xiàn)在在瀏覽器中打開是訪問不了容器中的http服務(wù)器的,因?yàn)槎丝跊]有被映射到主機(jī)端口上。
2.3 代理服務(wù)器實(shí)現(xiàn)
在此,為了驗(yàn)證我的想法是否能成功,只是實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的代理服務(wù)器,它將請(qǐng)求轉(zhuǎn)發(fā)的容器中,然后再轉(zhuǎn)發(fā)容器中服務(wù)器的響應(yīng)。(因?yàn)榇矸?wù)器是直接運(yùn)行在主機(jī)上的,因此可以通過容器IP+端口來訪問)
代碼如下:
package main
import (
"fmt"
"io"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.Default()
engine.GET("/proxy/*all", func(ctx *gin.Context) {
all := ctx.Param("all") // 獲取/proxy后面的參數(shù)
if len(all) <= 0 {
ctx.Status(http.StatusBadRequest)
return
}
all = all[1:] // 丟棄第一個(gè)'/'
idx := strings.Index(all, "/")
var url string
if idx < 0 { // 只有端口
url = fmt.Sprintf("http://172.17.0.3:%s", all)
} else { // 有端口和其它訪問路徑
port := all[:idx]
url = fmt.Sprintf("http://172.17.0.3:%s%s", port, all[idx:])
}
resp, err := http.Get(url) // 訪問容器中的服務(wù)器
if err != nil {
ctx.Status(http.StatusBadRequest)
return
}
io.Copy(ctx.Writer, resp.Body) // 轉(zhuǎn)發(fā)響應(yīng)
})
engine.Run(":80")
}
在主機(jī)服務(wù)器上運(yùn)行代理服務(wù)器,不要使用容器來啟動(dòng):
go build nohup ./porxy_server & # 后臺(tái)運(yùn)行
然后我們?cè)賳?dòng)瀏覽器vscode中的服務(wù)器看是否可以訪問到:

選擇"是",然后在新彈出的窗口中就可以訪問到容器中的服務(wù)了:

這里實(shí)現(xiàn)的只是一個(gè)非常簡(jiǎn)易的版本,只是提供了一個(gè)這樣的思路。如何要實(shí)現(xiàn)一個(gè)類似Cloud Studio的云IDE要考慮的還要更多。
最終效果如下:

到此這篇關(guān)于云vscode搭建使用容器化部署的文章就介紹到這了,更多相關(guān)云vscode搭建內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VSCode 格式化縮進(jìn)代碼的實(shí)現(xiàn)
這篇文章主要介紹了VSCode 格式化縮進(jìn)代碼的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Jar包一鍵重啟的Shell腳本及新服務(wù)器部署的一些經(jīng)驗(yàn)分享
這篇文章主要介紹了Jar包一鍵重啟的Shell腳本及新服務(wù)器部署的一些經(jīng)驗(yàn),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Visual Studio和Visual Studio Code之間有什么區(qū)別
本文給大家介紹的是Visual Studio和Visual Studio Code之間有什么區(qū)別,希望對(duì)大家的學(xué)習(xí)能夠有所幫助2020-02-02
git 配置多個(gè)SSH-Key實(shí)現(xiàn)示例
這篇文章主要為大家介紹了git 配置多個(gè)SSH-Key實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
Scratch3.0二次開發(fā)之windows環(huán)境下打包成exe的流程
今天通過本文給大家分享Scratch3.0二次開發(fā)之windows環(huán)境下打包成exe的詳細(xì)流程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-08-08
linux系統(tǒng)使用vscode進(jìn)行qt開發(fā)的過程分享
最近在Linux上搞Qt,搞的一頭霧水,小編把整個(gè)過程記錄下,分享需要的朋友,如果大家對(duì)linux系統(tǒng)使用vscode進(jìn)行qt開發(fā)相關(guān)知識(shí)感興趣的朋友跟隨小編一起看看吧2021-12-12

