https 是如何建立连接的

一、什么是HTTPS、TLS、SSL

HTTPS,也称作HTTP over TLS。TLS的前身是SSL,TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3。下图描述了在TCP/IP协议栈中TLS(各子协议)和HTTP的关系。

二、HTTP和HTTPS协议的区别

1、HTTPS协议需要到证书颁发机构(Certificate Authority,简称CA)申请证书,一般免费证书很少,需要交费。

2、HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议。

3、HTTP和HTTPS使用的是完全不同的连接方式,使用的端口也不一样,前者是80,后者是443。

4、HTTP的连接很简单,是无状态的。

5、HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTP协议安全。

从上面可看出,HTTPS和HTTP协议相比提供了

· 数据完整性:内容传输经过完整性校验

· 数据隐私性:内容经过对称加密,每个连接生成一个唯一的加密密钥

· 身份认证:第三方无法伪造服务端(客户端)身份

其中,数据完整性和隐私性由TLS Record Protocol保证,身份认证由TLS Handshaking Protocols实现。

三、证书

1、什么是证书呢?

2、证书中包含什么信息

证书信息:过期时间和序列号

所有者信息:姓名等

所有者公钥

3、为什么服务端要发送证书给客户端

互联网有太多的服务需要使用证书来验证身份,以至于客户端(操作系统或浏览器等)无法内置所有证书,需要通过服务端将证书发送给客户端。

4、客户端为什么要验证接收到的证书

中间人攻击

5、客户端如何验证接收到的证书

为了回答这个问题,需要引入数字签名(Digital Signature)。

将一段文本通过哈希(hash)和私钥加密处理后生成数字签名。

假设消息传递在Bob,Susan和Pat三人之间发生。Susan将消息连同数字签名一起发送给Bob,Bob接收到消息后,可以这样验证接收到的消息就是Susan发送的

当然,这个前提是Bob知道Susan的公钥。更重要的是,和消息本身一样,公钥不能在不安全的网络中直接发送给Bob。

此时就引入了证书颁发机构(Certificate Authority,简称CA),CA数量并不多,Bob客户端内置了所有受信任CA的证书。CA对Susan的公钥(和其他信息)数字签名后生成证书。

Susan将证书发送给Bob后,Bob通过CA证书的公钥验证证书签名。

Bob信任CA,CA信任Susan, 使得 Bob信任Susan,信任链(Chain Of Trust)就是这样形成的。

事实上,Bob客户端内置的是CA的根证书(Root Certificate),HTTPS协议中服务器会发送证书链(Certificate Chain)给客户端。

正式开始HTTPS的内容:

一、HTTPS的基本原理

从上面可知,HTTPS能够加密信息,以免敏感信息被第三方获取。所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。

HTTPS其实是有两部分组成:HTTP +SSL/ TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。具体是如何进行加密,解密,验证的,且看下图。

1. 客户端发起HTTPS请求

这个没什么好说的,就是用户在浏览器里输入一个HTTPS网址,然后连接到server的443端口。

2. 服务端的配置

采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面(startssl就是个不错的选择,有1年的免费服务)。这套证书其实就是一对公钥和私钥。如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。

3. 传送证书

这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。

4. 客户端解析证书

这部分工作是由客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随机值。然后用证书对该随机值进行加密。就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。

5. 传送加密信息

这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。

6. 服务端解密信息

服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密。所谓对称加密就是,将信息和私钥(随机值)通过某种算法混合在一起,这样除非知道私钥(随机值),不然无法获取内容,而正好客户端和服务端都知道这个私钥(随机值),所以只要加密算法够彪悍,私钥(随机值)够复杂,数据就够安全。

7. 传输加密后的信息

这部分信息是服务端用私钥(随机值)加密后的信息,可以在客户端被还原

8. 客户端解密信息

客户端用之前生成的私钥(随机值)解密服务端传过来的信息,于是获取了解密后的内容。整个过程第三方即使监听到了数据,也束手无策。

二、HTTPS的通信流程和握手过程

1、HTTPS对应的通信时序图:

2、HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。TLS/SSL协议不仅仅是一套加密传输的协议,更是一件经过艺术家精心设计的艺术品,TLS/SSL中使用了非对称加密,对称加密以及HASH算法。握手过程的具体描述如下:

1. 浏览器将自己支持的一套加密规则发送给网站。

2.网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。

3.浏览器获得网站证书之后浏览器要做以下工作:

a) 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。

b) 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。

c) 使用约定好的HASH算法计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。

4.网站接收浏览器发来的数据之后要做以下的操作:

a) 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。

b) 使用密码加密一段握手消息,发送给浏览器。

5.浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。

实现 Linux 终端录屏转gif动画

在一些开源其中,有些文档使用git动画来介绍的话效果会好很多,所以这里把在Linux终端下如何生成git动画效果整理出来,供大家参考。

安装录屏软件 asciinema

Mac

 brew install asciinema

Ubuntu

 sudo apt-add-repository ppa:zanchey/asciinema

Debian

 sudo apt-get install asciinema

Pip安装

 sudo pip3 install asciinema

目前此软件不支持 Windows。更多安装教程参考:https://zhuanlan.zhihu.com/p/28423868

用法介绍

 ❯ asciinema -h
 usage: asciinema [-h] [--version] {rec,play,cat,upload,auth} ...
 ​
 Record and share your terminal sessions, the right way.
 ​
 positional arguments:
  {rec,play,cat,upload,auth}
    rec                 Record terminal session
    play               Replay terminal session
    cat                 Print full output of terminal session
    upload             Upload locally saved terminal session to asciinema.org
    auth               Manage recordings on asciinema.org account
 ​
 optional arguments:
  -h, --help           show this help message and exit
  --version             show program's version number and exit
 ​
 example usage:
  Record terminal and upload it to asciinema.org:
    asciinema rec
  Record terminal to local file:
    asciinema rec demo.cast
  Record terminal and upload it to asciinema.org, specifying title:
    asciinema rec -t "My git tutorial"
  Record terminal to local file, limiting idle time to max 2.5 sec:
    asciinema rec -i 2.5 demo.cast
  Replay terminal recording from local file:
    asciinema play demo.cast
  Replay terminal recording hosted on asciinema.org:
    asciinema play https://asciinema.org/a/difqlgx86ym6emrmd8u62yqu8
  Print full output of recorded session:
    asciinema cat demo.cast
 ​
 For help on a specific command run:
  asciinema <command> -h
 ​

录屏

录屏命令

 asciinema rec

此时生成的文件将保存到临时目录里,一般为 /tmp/目录

也可以指定文件名

 asciinema rec demo.cast

当看到以下信息表示录屏工作开始,以后的操作将会被记录下来

 asciinema: recording asciicast to /tmp/tmpg4auzrud-ascii.cast
 asciinema: press <ctrl-d> or type "exit" when you're done

此时你可以进行正常的操作。

当操作完成后,按 ctrol-dexit退出录屏,看到提示信息

 asciinema: recording finished
 asciinema: press <enter> to upload to asciinema.org, <ctrl-c> to save locally
 asciinema: asciicast saved to /tmp/tmp1tj9jqnx-ascii.cast

如果按enter键会将结束自动上传到 asciinema.org网站,按下Ctrl+C 表示进行本地存储, 这里存储位置为 /tmp/tmp1tj9jqnx-ascii.cast

这种方法会在用户本地生成一个 .cast的文件,后面我们对其进行回放。

回放

我们先预览下上面生成的动画效果

 asciinema play demo.cast

查看会话

有时候我们需要查看用户的所有终端历史会话内容,此时可执行命令

 asciinema cat demo.cast

如果在屏幕过程中存在一些特殊命令,如 vi,则会话内容将显示成为乱码,还有可能提示错误。

上传

我们也可以将本地生成的文件上传到公网

asciinema upload demo.cast

提示

asciinema upload demo.cast
View the recording at:

    https://asciinema.org/a/KG2utenPw4pXk12TcEprPDaRh

This installation of asciinema recorder hasn't been linked to any asciinema.org
account. All unclaimed recordings (from unknown installations like this one)
are automatically archived 7 days after upload.

If you want to preserve all recordings made on this machine, connect this
installation with asciinema.org account by opening the following link:

    https://asciinema.org/connect/4fc6bdf3-ecc4-445a-a045-540aa101dee1

我们可以直接在浏览器里访问上面的URL来访问生成的效果。

转成Gif

有时候我们需要将上面的录屏内容转成gif格式在网络上传播,这时我们还需要利用一些工具将其转为gif动画才可以。这里我们使用一个docker镜像 asciinema/asciicast2gif 来操作

下载镜像

下载 Docker 镜像到本地

 docker pull asciinema/asciicast2gif

转换命令

docker run --rm -v $PWD:/data asciinema/asciicast2gif -s 2 -t solarized-dark demo.cast demo.gif

为了方便,我们用命令别名操作,将以下代码保存到 .bashrc 文件中,最后再执行 source ~/.bashrc 应用配置(如果用的zsh的话,则需要保存到 .zshrc文件)

 alias asciicast2gif='docker run --rm -v $PWD:/data asciinema/asciicast2gif'

以后就可以直接使用命令

 asciicast2gif demo.cast demo.gif

这里将录屏生成的json文件demo.json转成 demo.gif 文件,这时我们可以看下gif的生成效果。

另外在生成动画的时候,也可以指定一些参数,如倍速、缩放比例、高度和宽度,如

 asciicast2gif -t solarized-dark -s 2 -S 1 -w 400 -h 500 demo.cast demo.gif

参数

-t 表示颜色方案,必须为 asciinema, tango, solarized-dark, solarized-light, monokai (default: asciinema) 其中的一个,默认方案是 asciinema

-s 表示动画速度,默认为1

-S 图像比例/像素密度(默认值:2)

-w 将端子剪裁到指定的列数(宽度)

-h 将终端剪裁到指定的行数(高度)

常见问题

有时候在转gif的出现失败的情况,如果指定了一些参数的话,可以试着将参数移除试看看。我在用的时候经常出现在指定宽度和高度参数的时候会转换失败,将这两个参数省略则没有问题,怀疑是需要宽高不合理造成的。

参考资料

用 Goalng 开发 OPA 策略

Open Policy Agent 简称OPA是一个开源的通用策略引擎,可在整个堆栈中实现统一的、上下文感知的策略实施。OPA 已经成为了云原生计算基金会 (CNCF) 领域的毕业项目,已经在 Kubernetes / Istio 等多个知名项目里使用 。

OPA的核心思想就是策略即代码。

它使用Rego语言开发,Rego 的灵感来自 Datalog,它是一种易于理解、已有数十年的历史的查询语言。Rego 扩展了 Datalog 以支持 JSON 等文档模型。对于它的详细介绍请参考官方文档 https://www.openpolicyagent.org/docs/latest/policy-language/#what-is-rego,这里不再介绍,本方主要介绍如何使用Golang 来开发一个opa策略。

概述

OPA 将策略决策与策略执行分离。当您的软件需要做出策略决策时,它会查询 OPA 并提供结构化数据(例如 JSON)作为输入。 OPA 接受任意结构化数据作为输入。

对于它的输入,一般称为 input 可以为任何类型,输出也一样可以为任意类型,即可以输出布尔值truefalse,也可以输出一个 JSON 字符串对象。

Continue reading

一文看懂Golang 定时器源码

计时器分 Timer 和 Ticker 两种,它们底层基本是一样的,两差的区别请参考 https://blog.haohtml.com/archives/19859, 这里我们的介绍对象是 Timer 。

golang timer

计时器结构体

https://github.com/golang/go/blob/go1.17.6/src/time/sleep.go#L84-L98

 // NewTimer creates a new Timer that will send
 // the current time on its channel after at least duration d.
 func NewTimer(d Duration) *Timer {
     c := make(chan Time, 1)
     t := &Timer{
         C: c,
         r: runtimeTimer{
             when: when(d),
             f:    sendTime,
             arg:  c,
         },
     }
     startTimer(&t.r)
     return t
 }

通过调用 NewTimer() 函数创建一个 Timer,首先创建一个长度为1的有缓冲channel,再创建一个Timer的结构体,并将 channel 置于 Timer 结构体内。

注意这里的 runtimeTimer.f 字段是一个函数 sendTime ,其实现如下

func sendTime(c interface{}, seq uintptr) {
	// Non-blocking send of time on c.
	// Used in NewTimer, it cannot block anyway (buffer).
	// Used in NewTicker, dropping sends on the floor is
	// the desired behavior when the reader gets behind,
	// because the sends are periodic.
	select {
	case c.(chan Time) <- Now():
	default:
	}
}

当 sendTime 函数主要用在 newTimer() 时,它以无阻塞的方式将当前时间 Now() 发送到 c 通道里。如果用在 newTicker() 时,如果读取落后,会将发送丢弃,它是周期性的。

我们给出 Timer 的结构体声明。

 type Timer struct {
  C <-chan Time
  r runtimeTimer
 }

一共两个字段,为了理解方面我们称 runtimeTimer 为 timer 值。

我们再看一下其中的 runtimeTimer 结构体的声明

 // Interface to timers implemented in package runtime.
 // Must be in sync with ../runtime/time.go:/^type timer
 type runtimeTimer struct {
  pp       uintptr
  when     int64
  period   int64
  f        func(interface{}, uintptr) // NOTE: must not be closure
  arg      interface{}
  seq      uintptr
  nextwhen int64
  status   uint32
 }

对于 runnerTimer结构体要与在 runtime/time.go 文件中的 timer 结构体保持同步。

结构体字段说明

  • pp 指针类型,这里指 GPM 中的 P。如果这个计时器 timer 在一个heap 上,它在哪个 P 的堆上
  • when 表示唤醒执行的时间,表示什么时间开始执行
  • period 周期,一定是大于 0; when+period 表示下次唤醒执行的时间
  • f 执行函数,不允许为匿名函数,最好为非阻塞函数
  • arg 上面f函数的参数
  • seq 同 arg,其在 runOneTimer 函数中的调用方式为 f(arg, seq)
  • nextwhen 下次运行的时间,其值只有在 timerModifiedXX status 状态下才设置
  • status 状态,其定义的的可用值有10种,定义在 runtime/time.go,我们下面对这些状态进行了介绍。

每次开启一个goroutine 执行 f(arg, now),基中when表示执行的时间,而 when+period 表示下次执行的时间。(这时有点疑问,对调用的函数参数,f的第二个参数是 now, 但后面介绍的时候第二个参数却是 seq)

通过查看 https://github.com/golang/go/blob/go1.17.6/src/runtime/time.go#L41-L116 可知以下几点:

Continue reading

了解BPF技术

插桩技术

BPF支持多种事件源,可以在整个软件栈中提供能见度,其实现目前主要通过两种技术,分别为动态插桩静态插桩,有时候只用其中一种方式是无法实现我们的需求,这时就需要两者相互配合使用了。

动态插桩: kprobes 和 uprobes

动态插桩方式可以做到在程序运行期间,动态的插入观察点,而软件的运行不会受到任何影响,在这点做到了零开销。

kprobes 一般指内核态级别的函数插桩,而 uprobes 则指用户态级别的函数插桩。

动态插桩技术一般需要在 内核函数应用函数开始位置结束位置进行插桩。例如

#!/usr/local/bin/bpftrace

// this program times vfs_read()

kprobe:vfs_read
{
@start[tid] = nsecs;
}

kretbrobe:vfs_read
/@start[tid]/
{
$duration_us = (nsecs - @start[tid]) / 1000;
@us = hist($duration_us);
delete(@start[tid]));
}

这里kprobe:vfs_read 表示是内核函数bfs_read的起始插桩,而kretbroke:vfs_read则是函数的结果插桩,通过对这两个地方分别插桩就可以计算出当前函数的执行时间。

被插桩的函数在整个软件栈中有成千上万个,所以我们可以在任意关注的地方进行插桩,并编写自己的代码实现想要的功能。

动态插桩技术有一点不好的地方就是随着软件版本的迭代变更,被插桩的函数有可能会被重命名或者被移除,这时候会导致一些BPF工具没有办法直接使用,要想继续使用这些BPF工具只能跟着将其进行调整,这个问题十分令人头疼;除此之外还有一个问题就是编译器可能会将一些函数进行 inline 化,这时候会导致这些函数无法使用 kprobesuprobes 动态插桩。

静态插桩: tracepoint 和 USDT

静态插桩会将一些稳定的事件名字编码到软件代码中,由开发者自行维护。BPF跟踪工具支持内核的静态插桩技术,也支持用户态的静态定义跟踪插桩 USDT(user level statically defined tracing)。

不过静态插桩技术也存在一定的问题,那就是会增加开发者的维护成本,同时静态插桩点数量一般很有限。

所有如果我们要开发BPF工具的话,一般都推荐优先使用静态插桩技术,如果仍无法满足需求的话,再考虑使用动态插桩技(kprobes 或 uprobes)

开发工具

直接通过BPF指令编写BPF程序是件非常繁琐的一件事,因此出现了一些高级语言支持的BPF前端工具,主流开发工具主要是 BCCbpftrade

bcc、bpftrace 与bpf
bcc、bpftrace 与bpf

BCC

BBC(BPF编译器集合,BPF Compiler Collection) 是最早的开发BPF的开发框架。它提供了一个编写内核BPF程序的C语言环境,同时还提供了其它高级语言如Python、Lua 或 C++环境来实现用户端接口,它也是 libbcclibbpf 库的前身,这两个库提供了使用BPF程序对事件进行观测的库函数。

用户可以直接在系统上安装BCC即可,不用手动亲自编码代码就可以直接使用自带的一些工具。

bpftrace

bpftrace是一个新兴的前端,其源代码非常简洁,它同样也是基于 libbcclibbpf 库构建的。

对比

BCC 与 bpftrace 两者具有互补性,bpftrace在编写功能强大的单行程序或短小的脚本方便十分理解;BCC则更适合开发大型复杂的脚本和作为后台进程使用,同时它还可以调用其它的库。比如目前很多用Python来开发BCC程序,它们使用Python的 argparse 库来提供复杂的命令行参数运行。

BCC 和 bpftrace 它们并不属于内核代码仓库的项目,而是托管在 Github 上一个名为 IO Visor的Linux基金会。

推荐阅读:https://www.brendangregg.com/ebpf.html

参考资料

Golang常见编译参数

在执行 go build 命令的时候,经常需要添加一些参数,或许是为了调试,也或许是为了生成最终部署。

如果只在编译特定包时需要传递参数,格式应遵守“包名=参数列表”,如

go build -gcflags -gcflags='log=-N -l' main.go

-gcflags

go build 可以用 -gcflagsgo编译器传入参数,也就是传给 go tool compile 的参数,因此可以用 go tool compile –help 查看所有可用的参数。

其中 -m 可以检查代码的编译优化情况,包括逃逸情况和函数是否内联。

-ldflags

go build用 -ldflags 给go链接器传入参数,实际是给go tool link的参数,可以用go tool link –help查看可用的参数。

常用-X来指定版本号等编译时才决定的参数值。例如代码中定义var buildVer string,然后在编译时用go build -ldflags “-X main.buildVer=1.0” … 来赋值。注意-X只能给string类型变量赋值。


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 仓库,其仓库是google提供的,由于国内网络环境的复杂性是无法拉取到这些镜像的,这时候就需要我们想一些办法来实现拉取了。

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

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

参考文档

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