Golang中的 CGO_ENABLED 环境变量

Golang中的编译参数

开发中经常使用 go build 命令来编译我们的程序源码,然后将生成二进制文件直接部署,极其方便。

对于 go build 有一些参数,对于针对程序源码进行一些编译优化,下面我们对经常使用的一些参数来介绍一下。

环境变量

环境变量需要在go命令前面设置,如果多个变量的话,中间需要用“空格”分隔。下面我们介绍一个非常常见到的一些环境变量

$ CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -o myserver main.go

除了这里给出的这几个变量外,还有一些其它变量,如 GODEBUG、GOFLAGS、GOPROXY 等,所有支持环境变量都可以在 https://github.com/golang/go/blob/a88575d662a7e8e4fbb31bf139bcffc063e2a734/src/cmd/go/internal/help/helpdoc.go#L485 里找到,有兴趣的话可以看看他们的作用。

这里重点介绍一下 CGO_ENABLED 环境变量对我们程序的影响。 CGO_ENABLED是用来控制golang 编译期间是否支持调用 cgo 命令的开关,其值为1或0,默认情况下值为1,可以用 go env 查看默认值。

如果你的程序里调用了cgo 命令,此参数必须设置为1,否则将编译时出错。这里直接用文档 https://go.dev/blog/cgo 中的一个例子验证。

package main

// #include <stdio.h>
// #include <stdlib.h>
//
// static void myprint(char* s) {
//   printf("%sn", s);
// }
import "C"
import "unsafe"

func main() {
	cs := C.CString("Hello from stdio")
	C.myprint(cs)
	C.free(unsafe.Pointer(cs))
}

然后我们执行一下 启用 CGO_ENABLED=1 的情况

root@ubuntu:/home/sxf/gotest# CGO_ENABLED=1 go run main.go
root@ubuntu:/home/sxf/gotest# ./main
Hello from stdio

可以看到输出正常,这里也可以省略不写变量,因为默认情况为启用CGO状态

启用 CGO_ENABLED=0 的情况

root@ubuntu:/home/sxf/gotest# CGO_ENABLED=0 go build main.go
go: no Go source files

可以看到编译失败,验证了我们上面说的情况。

这里提示找不到go源文件,不清楚底层是如何判断这一情况的。

那么,如果我们一个程序里未调用cgo,在编译时指定 CGO_ENABLED 不同值话,又会发生什么呢?编译的二进制有何区别呢?

root@ubuntu:/home/sxf/gotest# CGO_ENABLED=1 go build -o cgo_main main.go
root@ubuntu:/home/sxf/gotest# CGO_ENABLED=0 go build -o no_cgo_main main.go
root@ubuntu:/home/sxf/gotest# ls -al
total 3804
drwxrwxr-x  2 sxf  sxf     4096 Nov 25 10:22 .
drwxr-x--- 47 sxf  sxf     4096 Nov 19 12:48 ..
-rwxr-xr-x  1 root root 1937311 Nov 25 10:22 cgo_main
-rw-rw-r--  1 sxf  sxf      119 Oct 29 09:07 go.mod
-rw-rw-r--  1 sxf  sxf      241 Oct 29 09:07 go.sum
-rw-rw-r--  1 sxf  sxf       72 Nov 25 10:21 main.go
-rwxr-xr-x  1 root root 1937311 Nov 25 10:22 no_cgo_main

可以看到两种情况都可能编译成功,且两者生成的二进制文件是完全一样的。由此总结出可以看出如果程序里未调用 cgo 的话,此变量值并没有影响的。

那么问题来了,为什么这么多项目里编译的时候都明确指定了此环境变量的值呢,主要是编译器在编译时会根据不同的情况使用不同的编译方法。 当CGO_ENABLED=1,进行编译时会将文件中引用libc的库(比如常用的net包),以动态链接的方式生成目标文件。 当CGO_ENABLED=0,进行编译时则会把在目标文件中未定义的符号(外部函数)一起链接到可执行文件中。

不论哪种方式,都可以使用静态连接编译

参考

利用代理拉取docker镜像

在日常开发中,经常会遇到镜像在 gcr.io 仓库的镜像,由于国内网络环境的复杂性,是无法拉取到这些镜像的,这时候就需要我们想一些其它办法来实现了。

这里给出了两种解决方法,一种是直接使用代理,这种可以直接拉取远程镜像到本地。另一种是通过中转的方法,先找一个可以直接拉取到镜像的网络,先将存储到本地,然后再转镜像上传到三方国内可以访问的镜像,如我们最常用镜像 hub.docker.com。

代理方法

使用代理方法的时候,如果通过直接设置 http_proxy 和 https_proxy 这两个环境变量是不可行的。主要原因是 docker 并不会使用它们,需要为 docker daemon 服务的设置代理才可以。

设置docker服务代理

sudo mkdir -p /etc/systemd/system/docker.service.d/
sudo vim /etc/systemd/system/docker.service.d/http-proxy.conf

将以下内容写入 http-proxy.conf 文件

[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="ALL_PROXY=socks5://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,docker-registry.example.com,.corp,.docker.io,.docker.com"

上面代理地址是本机开启的代理服务监听端口,如果代理服务在局域网内的其它机器上的话,需要更换为其 ip 地址和端口号。环境变量 NO_PROXY 表示不使用代理的域名或IP。

重启 docker 服务

root@ubuntu:~# systemctl daemon-reload
root@ubuntu:~# systemctl restart docker

验证设置

root@ubuntu:~# systemctl show --property=Environment docker
Environment=HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 ALL_PROXY=socks5://127.0.0.1:7890 NO_PROXY=localhost,127.0.0.1,docker-registry.example.com,.corp,.docker.io,.docker.com

也可以使用命令 docker info 验证

root@ubuntu:~# docker info
...
 Debug Mode: false
 HTTP Proxy: http://127.0.0.1:7890
 HTTPS Proxy: http://127.0.0.1:7890
 No Proxy: localhost,127.0.0.1,docker-registry.example.com,.corp,.docker.io,.docker.com
...

拉取镜像

root@ubuntu:~# docker pull gcr.io/google_containers/pause-amd64:3.0
3.0: Pulling from google_containers/pause-amd64
a3ed95caeb02: Pull complete 
f11233434377: Pull complete 
Digest: sha256:163ac025575b775d1c0f9bf0bdd0f086883171eb475b5068e7defa4ca9e76516
Status: Downloaded newer image for gcr.io/google_containers/pause-amd64:3.0
gcr.io/google_containers/pause-amd64:3.0

此时使用命令 docker images 命令查看,会看到镜像下载成功,大小为747K。

中转方法

对于中转方法这里推荐使用 https://labs.play-with-docker.com,国内用户可以直接访问。方法比较简单,这里只给出几个用到的命令,不再详细介绍。

首先拉取镜像到本地,然后用docker tag 命令修改标签,并上传到 hub.docker.com 的个人账号仓库下

# docker pull gcr.io/google_containers/pause-amd64:3.0
# docker tag gcr.io/google_containers/pause-amd64:3.0 cfanbo/gcr.io_google_containers_pause-amd64:3.0 
# 登录 hub.docker.log
# docker login
# docker push cfanbo/gcr.io_google_containers_pause-amd64:3.0

本地从 hub.docker.com 拉取镜像, 再改为原来的名称+标签名

# docker pull cfanbo/gcr.io_google_containers_pause-amd64:3.0
# docker tag cfanbo/gcr.io_google_containers_pause-amd64:3.0 gcr.io/google_containers/pause-amd64:3.0

此时镜像已经拉取成功,当前本地存在两个完全一样的镜像,可以将原来使用的临时中转镜像删除。

参考文档

https://docs.docker.com/config/daemon/systemd/#httphttps-proxy

k8s解决证书过期问题

在k8s中的时间会提示证书过期问题,如

# kubectl get nodes
Unable to connect to the server: x509: certificate has expired or is not yet valid

这里我们介绍一下续期方法。

注意:当前集群通过 kubeadm 命令创建。

kubeadm 安装得证书默认为 1 年,注意原证书文件必须保留在服务器上才能做延期操作,否则就会重新生成,集群可能无法恢复

准备

这里先查看一下测试集群的证书过期时间

# kubeadm certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Aug 30, 2022 03:18 UTC   324d                                    no      
apiserver                  Aug 30, 2022 03:18 UTC   324d            ca                      no      
apiserver-etcd-client      Aug 30, 2022 03:18 UTC   324d            etcd-ca                 no      
apiserver-kubelet-client   Aug 30, 2022 03:18 UTC   324d            ca                      no      
controller-manager.conf    Aug 30, 2022 03:18 UTC   324d                                    no      
etcd-healthcheck-client    Aug 30, 2022 03:18 UTC   324d            etcd-ca                 no      
etcd-peer                  Aug 30, 2022 03:18 UTC   324d            etcd-ca                 no      
etcd-server                Aug 30, 2022 03:18 UTC   324d            etcd-ca                 no      
front-proxy-client         Aug 30, 2022 03:18 UTC   324d            front-proxy-ca          no      
scheduler.conf             Aug 30, 2022 03:18 UTC   324d                                    no      

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Aug 28, 2031 03:18 UTC   9y              no      
etcd-ca                 Aug 28, 2031 03:18 UTC   9y              no      
front-proxy-ca          Aug 28, 2031 03:18 UTC   9y              no  

可以看到过期时间为 2022-08-30。

Continue reading

istio在虚拟机vm下的安装方法

建议参考官方文档 https://istio.io/latest/zh/docs/setup/install/virtual-machine/ ,这里提醒大家对于命令中文版部分命令与英文版不一致,请以 英文版 为准。

对于istio在vm上的安装教程主要分为三部分。首先是在k8s的master节点生成vm连接主节点的一些配置信息,其实是在vm上应用这些配置信息,最后也就是验证连接是否成功。

本篇主要介绍“单网络”的情况, 对于”多网络“请自行参考官方文档。

vm环境准备

生成vm通讯配置信息

这里主要介绍一些新手迷惑的部分。如环境变量设置及vm注册的方式

设置环境变量

在设置变量时,对于”单网络“来讲 CLUSTER_NETWORK 和 VM_NETWORK 保留空值即可。如我这里设置如下

$ VM_APP="myapp"
$ VM_NAMESPACE="vm"
$ WORK_DIR="/root/myapp"
$ SERVICE_ACCOUNT="vm-sa"
$ CLUSTER_NETWORK=""
$ VM_NETWORK=""
$ CLUSTER="Kubernetes"

每个环境变量的解释:
VM_APP 表示vm上应用的名称
VM_NAMESPACE 表示应用所在的namespace
WORK_DIR 生成vm配置信息保留的目录,任何位置即可
SERVICE_ACCOUNT 服务运行的账号 ,即yaml文件中的 ServiceAccount 字段
CLUSTER 集群名称,默认为 Kubernetes 即可。

Continue reading

利用 docker buildx 构建多平台镜像

什么是 docker buildx

Docker Buildx是一个CLI插件,它扩展了Docker命令,完全支持Moby BuildKit builder toolkit提供的功能。它提供了与docker build相同的用户体验,并提供了许多新功能,如创建作用域生成器实例和针对多个节点并发构建。

Docker Buildx包含在Docker 19.03中,并与以下Docker Desktop版本捆绑在一起。请注意,必须启用“实验特性”选项才能使用Docker Buildx。

Docker Desktop Enterprise version 2.1.0
Docker Desktop Edge version 2.0.4.0 or higher

创建 builder 实例

由于 Docker 默认的 builder 实例不支持同时指定多个 –platform,所有我们必须先创建一个 builder 实例

$ docker buildx create --use --name=mybuilder-cn --driver docker-container

--use 表示使用当前创建的 builder 实例
--name 实例名称
--driver 实例驱动(docker、 docker-container 和 kubernetes)

更多用法通过命令 docker buildx create -h 查看

Continue reading

k8s安装负载均衡器:Metallb

除了本文介绍的 Metalldb 另,还有另一种解决方案为 openELB ,它是由国内互联网 青云 公司开源的一种方法,目前在 CNCF 沙盒 在托管,已在国内不少企业如 本来生活、苏州电视台、视源股份、云智天下、Jollychic、QingCloud、百旺、Rocketbyte 等海内外多家企业采用。

在使用kubenetes的过程中,如何将服务开放到集群外部访问是一个重要的问题。当使用云平台(阿里云、腾讯云、AWS等)的容器服务时,我们可以通过配置 service 为 LoadBalancer 模式来绑定云平台的负载均衡器,从而实现外网的访问。但是,如果对于自建的 kubernetes裸机集群,这个问题则要麻烦的多。

祼机集群不支持负载均衡的方式,可用的不外乎NodePort、HostNetwork、ExternalIPs等方式来实现外部访问。但这些方式并不完美,他们或多或少都存在的一些缺点,这使得裸机集群成为Kubernetes生态系统中的二等公民。

MetalLB 旨在通过提供与标准网络设备集成的Network LB实施来解决这个痛点,从而使裸机群集上的外部服务也尽可能“正常运行”,减少运维上的管理成本。它是一种纯软件的解决方案,参考 https://kubernetes.github.io/ingress-nginx/deploy/baremetal/

Continue reading

服务网格Istio之服务入口 ServiceEntry

使用服务入口(Service Entry) 来添加一个入口到 Istio 内部维护的服务注册中心。添加了服务入口后,Envoy 代理可以向服务发送流量,就好像它是网格内部的服务一样,可参考 https://istio.io/latest/zh/docs/concepts/traffic-management/#service-entries

简单的理解就是允许内网向外网服务发送流量请求,但你可能会说正常情况下在pod里也是可以访问外网的,这两者有什么区别呢?

确实默认情况下,Istio 配置 Envoy 代理将请求传递给未知服务。但是无法使用 Istio 的特性来控制没有在网格中注册的目标流量。这也正是 ServiceEntry 真正发挥的作用,通过配置服务入口允许您管理运行在网格外的服务的流量。

此外,可以配置虚拟服务和目标规则,以更精细的方式控制到服务条目的流量,就像为网格中的其他任何服务配置流量一样。

为了更好的理解这一块的内容,我们先看一下普通POD发送请求的流程图

普通 Pod 请求

Continue reading

在linux下安装Kubernetes

环境 ubuntu18.04 64位

为了解决国内访问一些国外网站慢的问题,本文使用了国内阿里云的镜像。

更换apt包源

这里使用aliyun镜像 https://developer.aliyun.com/mirror/, 为了安全起见,建议备份原来系统默认的 /etc/apt/sources.list 文件

编辑文件 /etc/apt/sources.list,将默认网址 http://archive.ubuntu.com 或 http://cn.archive.ubuntu.com 替换为 http://mirrors.aliyun.com

更新缓存

$ sudo apt-get clean all
$ sudo apt-get update

安装Docker

更改为 aliyun 镜像为 https://developer.aliyun.com/mirror/docker-ce,官方文档 https://docs.docker.com/engine/install/ubuntu/ ,

Continue reading

Golang中的runtime.LockOSThread 和 runtime.UnlockOSThread

在runtime中有 runtime.LockOSThreadruntime.UnlockOSThread 两个函数,这两个函数有什么作用呢?我们看一下标准库中对它们的解释。

runtime.LockOSThread

// LockOSThread wires the calling goroutine to its current operating system thread.
// The calling goroutine will always execute in that thread,
// and no other goroutine will execute in it,
// until the calling goroutine has made as many calls to
// UnlockOSThread as to LockOSThread.
// If the calling goroutine exits without unlocking the thread,
// the thread will be terminated.
//
// All init functions are run on the startup thread. Calling LockOSThread
// from an init function will cause the main function to be invoked on
// that thread.
//
// A goroutine should call LockOSThread before calling OS services or
// non-Go library functions that depend on per-thread state.

调用 LockOSThread绑定 当前 goroutine 到当前 操作系统线程,此 goroutine 将始终在此线程执行,其它 goroutine 则无法在此线程中得到执行,直到当前调用线程执行了 UnlockOSThread 为止(也就是说 LockOSThread 可以指定一个goroutine 独占 一个系统线程);

Continue reading