vscode code server 配置 golang 和 php 开发环境

前几天更新了一篇blog,记录了怎么搭建 vscode code server。在使用场景中呢,我们不会使用一个没有语言环境的IDE,某些 IDE 的插件就是依赖于语言环境的。我日常使用 golang 和 php 比较多,这篇文章记录如何在 code server 中配置 golang 和 php 环境。

一、编译镜像

基于上一篇文章,得到了镜像 kelvinblood/code-server:base,在这个镜像的基础上继续编译新镜像。

有以下几个注意点:

  1. 基础镜像为先前编译后的镜像 kelvinblood/code-server:base
  2. 配置了 golang 所需的环境变量 GOROOT 和 GOPATH,并打开了 GO111MODULE。
  3. 安装了 wget/ping/telnet 等常用的工具,方便定位问题。
  4. 安装了一些常用的 vscode 的 golang 插件。
  5. 安装了 go版本 1.16.3, php 版本7.1.5 和 最新版的composer。
FROM kelvinblood/code-server:base

ARG GOLANG_VERSION=1.16.3
ARG PHP_VERSION=7.1.5
ENV GOROOT /usr/local/go
ENV GOPATH /var/local/go
ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin
ENV GO111MODULE on
ENV GOPROXY https://goproxy.cn

RUN \
 echo "**** install tools ****" && \
 apt-get update && \
 apt-get install -y \
	wget \
	iputils-ping \
	xinetd telnetd && \
 echo "**** install golang ****" && \
 GO_TGZ=go$(echo "$GOLANG_VERSION").linux-amd64.tar.gz && \
 wget https://golang.org/dl/${GO_TGZ} && \
 tar zxvf ${GO_TGZ} && \
 mv go /usr/local && \
 echo "**** go get for vscode ****" && \
 go get -v golang.org/x/tools/gopls && \
 go get -v github.com/ramya-rao-a/go-outline && \
# go get github.com/uudashr/gopkgs/cmd/gopkgs@latest && \
 go get -v github.com/cweill/gotests/gotests && \
 go get -v github.com/fatih/gomodifytags && \
 go get -v github.com/josharian/impl && \
 go get -v github.com/haya14busa/goplay/cmd/goplay && \
 go get -v github.com/go-delve/delve/cmd/dlv && \
 go get -v honnef.co/go/tools/cmd/staticcheck && \
 go get -v golang.org/x/tools/gopls && \
 echo "**** install php ****" && \
  apt-get -y install libssl-dev libcurl4-openssl-dev libbz2-dev libjpeg-dev libpng-dev libgmp-dev libicu-dev libmcrypt-dev freetds-dev libxslt-dev libcurl3-dev autoconf dpkg-dev file g++ gcc libc-dev make pkg-config re2c && \
  ln -s /usr/lib/x86_64-linux-gnu/libsybdb.a /usr/lib/libsybdb.a && \
  ln -s /usr/lib/x86_64-linux-gnu/libsybdb.so /usr/lib/libsybdb.so && \
  ln -s /usr/lib/x86_64-linux-gnu/libct.a /usr/lib/libct.a && \
  ln -s /usr/lib/x86_64-linux-gnu/libct.so /usr/lib/libct.so && \
  ln -s /usr/include/x86_64-linux-gnu/gmp.h /usr/include/gmp.h && \
  ln -s /usr/include/x86_64-linux-gnu/curl /usr/include/curl && \
  PHP_TGZ=php-${PHP_VERSION}.tar.gz && \
  wget http://am1.php.net/distributions/${PHP_TGZ} && \
  tar -xzvf ${PHP_TGZ} && \
  cd php-${PHP_VERSION} && \
  ./configure --prefix /usr/share/php7 --enable-fpm --enable-mbstring --enable-zip --enable-calendar --enable-bcmath --enable-exif --enable-intl --enable-opcache --enable-shmop --enable-soap --enable-sockets --with-fpm-user=www-data --with-fpm-group=www-data --with-pcre-regex --with-kerberos --with-openssl --with-mcrypt --with-zlib --with-bz2 --with-curl --with-gd --with-jpeg-dir=/usr/include/jpeg8 --with-png-dir=/usr/include/libpng12 --with-gettext --with-gmp --with-mhash --with-xsl && \
  make clean && \
  make && make install && \
  make test && \
  make clean && \
  ln -s /usr/share/php7/sbin/php-fpm /usr/local/bin/php-fpm && \
  ln -s /usr/share/php7/bin/php /usr/local/bin/php && \
  cd .. && \
  curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer && \
 echo "**** clean up ****" && \
 pwd && \
 apt-get clean && \
 rm -rf \
	/tmp/* \
	/var/lib/apt/lists/* \
	/var/tmp/* \
        ${GO_TGZ} \
        ${PHP_TGZ}

docker build 需要比较长的时间,主要看机器性能和网络速度,需要一点耐心,快的也要10分钟,慢的大概要40分钟。 php 编译需要的内存比较高,如果只有1G内存的话最好把 swap 打开。

相应的 docker-compose.yml 文件和先前的没有差别。

二、常用插件

如下图所示,重要的就是这几个:

  • vim 是必不可少的
  • 简体汉化包
  • go包
  • laravel包
  • php Debug包

搭建个人在线IDE —— vscode online

前年,微软在 Ignite 2019 大会上,正式发布了 「Visual Studio Online」。其中包含了微软托管的 WebVSCode,后来发布的 VSCode 1.40 支持开发者直接从 VSCode 的源代码编译出 WebVSCode

由于我目前开发环境众多,win/mac/Linux都有,虽然已经对代码做了容器化,还是需要重复下载代码,配置IDE环境,挺麻烦的。这篇文章简单记录我搭建在线IDE的过程。

安装

配置VSCode Online有几种方法:

  • 微软官方提供一个收费版本(含azure的服务器费用,捆绑销售),不推荐
  • 下载 VSCode 源代码,编译以后通过yarn web启动。配置难度大,不推荐
  • 通过 Code-Server 安装: https://code-server.dev/
  • 使用/修改现成的 docker 镜像:linuxserver/docker-code-server

我目前的方法是自行编译 linuxserver 提供的镜像。实际上只用linuxserver 的镜像已经足够了,没有特殊自定义要求的可以跳过这一部分。

这个文件是 code-server实际的启动命令,可以按需自定义修改:

#!/usr/bin/with-contenv bash

if [ -n "${PASSWORD}" ] || [ -n "${HASHED_PASSWORD}" ]; then
  AUTH="password"
else
  AUTH="none"
  echo "starting with no password"
fi

if [ -z ${PROXY_DOMAIN+x} ]; then
  PROXY_DOMAIN_ARG=""
else
  PROXY_DOMAIN_ARG="--proxy-domain=${PROXY_DOMAIN}"
fi

exec \
	s6-setuidgid abc \
		/usr/local/bin/code-server \
			--bind-addr 0.0.0.0:8443 \
			--user-data-dir /config/data \
			--extensions-dir /config/extensions \
			--disable-telemetry \
			--auth "${AUTH}" \
			"${PROXY_DOMAIN_ARG}" \
			/config/workspace

例如我将端口由 8443 改成了 80。

--bind-addr 0.0.0.0:80

修改 dockerfile 中的 expose 内容

EXPOSE 8443

改为

EXPOSE 80

在项目主目录进行编译:

docker build \
  --no-cache \
  --pull \
  -t kelvinblood/code-server:base .

运行

参考 https://github.com/linuxserver/docker-code-server 的帮助内容,我做了调整:

docker-compose.yml:

version: "2.1"
services:
  code-server:
    image: kelvinblood/code-server:base
    container_name: code-server
    network_mode: bridge
    environment:
      - PUID=0
      - PGID=0
      - TZ=Asia/Shanghai
      - PASSWORD=xxxx  #optional
#      - HASHED_PASSWORD= #optional
#      - SUDO_PASSWORD=abcd #optional
#      - SUDO_PASSWORD_HASH= #optional
      - PROXY_DOMAIN=xxx.kelu.org #optional
    volumes:
      - ./config:/config
      - /root/abc:/Workspace/abc
      - /root/xxx:/Workspace/xxx
#    ports:
#      - xxxx:80
    restart: unless-stopped

配置nginx

image-20210407170010631

在这一块我做多了几个安全认证,这几项都不是必须的,可以跳过,一个是使用https访问:

  ssl_certificate /etc/letsencrypt/live/code.kelu.org/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/code.kelu.org/privkey.pem;
  ssl_session_timeout 5m;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers AESGCM:ALL:!DH:!EXPORT:!RC4:+HIGH:!MEDIUM:!LOW:!aNULL:!eNULL;
  ssl_prefer_server_ciphers on;

一个是nginx密码

  auth_basic "Please input password";
  auth_basic_user_file /etc/nginx/passwd/goaccess;

一个是允许特定IP访问:

     satisfy any;
     allow xxx;
     allow xxx;
     deny all;

最后要注意配置 websocket 代理配置,最后两行:

  location / {
     satisfy any;
     allow 121.31.34.22;
     allow 61.51.94.34;
     deny all;

     proxy_pass http://code-server.bj1.local;
     proxy_read_timeout 300s;
     proxy_send_timeout 300s;

     proxy_set_header Host $host;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     
     proxy_set_header Accept-Encoding gzip;

     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection "upgrade";
  }
}

界面预览

image-20210407170700847

装上自己需要的插件,就可以开始开发了。

image-20210407171100100

参考资料


go 字符串拆分 split

简单记录 golang 使用 regexp.MustCompile 进行字符串拆分的用法。

假设目前的输入值为:

        "spec":{  
            "Container": {
                "default.cpu": "200m",
                "default.memory": "200Mi",
                "defaultRequest.cpu": "100m",
                "defaultRequest.memory": "100Mi",
                "max.cpu": "2",
                "max.memory": "1Gi",
                "maxLimitRequestRatio.cpu": "5",
                "maxLimitRequestRatio.memory": "4",
                "min.cpu": "100m",
                "min.memory": "3Mi"
            },
            "Pod": {
                "max.cpu": "2",
                "max.memory": "1Gi",
                "maxLimitRequestRatio.cpu": "5",
                "maxLimitRequestRatio.memory": "4",
                "min.cpu": "100m",
                "min.memory": "3Mi"
            }
        }

我们将 . 号前后的字符进行拆分,整合 key,append 最后输出为类似如下格式的 interface:

image-20210323120833531

嵌套两个 for 循环即可完成。

func exchange(spec interface{}) interface{} {
    var result []interface{}

	for kind, u := range spec.(map[string]interface{}) {
		limitsRule := make(map[string]interface{})
		limitsRule["type"] = kind
		for k, _ := range u.(map[string]interface{}) {
			s := regexp.MustCompile("[.!?]").Split(k, 2)
			limitsRule[s[0]] = nil
		}

		for k, v := range u.(map[string]interface{}) {
			s := regexp.MustCompile("[.!?]").Split(k, 2)
			var tmp map[string]string
			if limitsRule[s[0]] == nil {
				tmp = make(map[string]string)
			} else {
				tmp = limitsRule[s[0]].(map[string]string)
			}
			tmp[s[1]] = v.(string)
			limitsRule[s[0]] = tmp
		}

		result = append(result, limitsRule)
	}

	return result
}

参考资料


go 使用 ast 优化结构体打印样式

与 kubernetes 代码交互,常有难以名状的interface,需要猜测里面的数据,非常痛苦。使用 ast 打印可以一目了然里面的数据,是开发过程中不得或缺的辅助工具。这篇文章简单记录我的使用情况。


package xxx

import (
	"go/ast"
	"go/token"	
)

func GuessType(obj interface{}) {
	fset := token.NewFileSet()
	ast.Print(fset, obj)
}

将 obj 传入 GuessType 方法即可:

GuessType(obj)

参考资料


go 基本类型的互转

转转转,类型转换是静态语言(强类型语言)在代码时候经常要进行的动作。这篇文章是我 golang 开发中类型转换的一些笔记。interface、struct、map相关的转换参考前文《golang 解析 map[string]interface{} 和 json 到 struct》

package xxx

import (
    "strconv"
)

int -> string

// int64
var Int64 int64 = 9223372036854775807
str := strconv.FormatInt(Int64, 10)

// unit64
var Uint64 uint64 = 18446744073709551615
str2 := strconv.FormatUint(Uint64, 10)

string -> int

// int64
var strInt64 string = "9223372036854775807"
convertedStrInt64, _ := strconv.ParseInt(strInt64, 10, 64)

// uint64
var strUint64 string = "18446744073709551615"
convertedStrUint64, _ := strconv.ParseUint(strUint64, 10, 64)

interface -> map[string] interface

// data 为 interface
data.(map[string]interface{})

interface -> string

// data 为 interface
data.(string)

参考资料


将 Kubernetes unstructured 类型转为对象

使用代码和k8s交互,主要用到 client-go 这个库中的两个主要 APIs: kubernetes.Interface API 和非结构化的 dynamic.Interface API。

在使用 dynamic.Interface 时,我们经常需要在 string,unstructured ,对象, map[string] interface{} 转来转去,本文介绍将非结构化的类型 (unstructured) 与对象互转的两个方法。

方法一:json

使用 json 包将 unstructured 序列化为 []byte ,再反序列化为对象,这是 golang 里最通用的做法了。

unstructured -> []byte -> object

import "encoding/json"
...

    // 从k8s返回值里构造一个 unstructured 
    var utd *unstructured.Unstructured
	utd, err = dynamicClient.Resource(gvr).Create(xxxUnstructured, metav1.CreateOptions{})
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	
    // unstructured -> []byte
	result, err := utd.MarshalJSON()
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

    // []byte -> obj
	var obj XxxObject
	if err := json.Unmarshal(result, &obj); err != nil {
		logrus.Error(err)
		return nil, err
	}

object -> []byte -> unstructured

ps: 对象转为[]byte时需要注意的一点是,object里不能存在 map[interface{}] interface{} 类型,如果存在此种类型,一定要在源头将其转换为 map[string]interface 类型,否则无法使用json包。

json: unsupported type: map[interface {}]interface {}

相关 issue参考:map[interface{}]interface{} on a map[string]interface{}

import "gopkg.in/yaml.v3"
...

    //// 从k8s返回值里构造一个 object 
	var obj XxxObj
	err := yaml.Unmarshal([]byte(yamlData), &obj)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

    // obj ->  []byte
	data, err := json.Marshal(&obj)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	
	// []byte -> Unstructured
	utd := &unstructured.Unstructured{}
	err := json.Unmarshal(data, &utd.Object)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}