个人笔记

专注互联网

errors库

Pkg/errors

package main

import (
"fmt"

"github.com/pkg/errors"
)

func Func() error {
return fmt.Errorf("oring error")
}

func Func1() error {
if err := Func(); err != nil {
return errors.WithMessage(err, "Func1")
}
return nil
}

func Func2() error {
err := Func1()
if err != nil {
return errors.Wrap(err, "Func2")
}
return nil
}

func main() {
if err := Func2(); err != nil {
fmt.Print(err)
fmt.Println("\n------\n")
fmt.Printf("%+v", err)
return
}
}
  • New(message string) 生成的错误,自带调用堆栈信息 // errors.New/fmt.Errorf
  • WithMessage(err error, message string) 只附加新的信息
  • func WithStack(err error) error //只附加调用堆栈信息
  • func Wrap(err error, message string) error //同时附加堆栈和信息

func Cause(err error) error 获取原始错误

Print

fmt.Printf

  • %s,%v 功能一样,输出错误信息,不包含堆栈
  • %q 输出的错误信息带引号,不包含堆栈
  • %+v 输出错误信息和堆栈

juju/errors

package main

import (
"fmt"

"github.com/juju/errors"
)

func Func() error {
return fmt.Errorf("oring error")
}

func Func1() error {
if err := Func(); err != nil {
return errors.Annotate(err, "Func1")
}
return nil
}

func Func2() error {
err := Func1()
if err != nil {
return errors.Trace(err)
}
return nil
}

func main() {
if err := Func2(); err != nil {
fmt.Print(err)
fmt.Println("\n------\n")
fmt.Printf("%+v", err)
return
}
}

GOPROXY

$go env -w GOPROXY=https://goproxy.cn,direct
$export GOPROXY=https://goproxy.cn

mod replace

replace (
golang.org/x/build => github.com/golang/build v0.0.0-20190416225751-b5f252a0a7dd
golang.org/x/crypto => github.com/golang/crypto v0.0.0-20190411191339-88737f569e3a
golang.org/x/exp => github.com/golang/exp v0.0.0-20190413192849-7f338f571082
golang.org/x/image => github.com/golang/image v0.0.0-20190417020941-4e30a6eb7d9a
golang.org/x/lint => github.com/golang/lint v0.0.0-20190409202823-959b441ac422
golang.org/x/mobile => github.com/golang/mobile v0.0.0-20190415191353-3e0bab5405d6
golang.org/x/net => github.com/golang/net v0.0.0-20190415214537-1da14a5a36f2
golang.org/x/oauth2 => github.com/golang/oauth2 v0.0.0-20190402181905-9f3314589c9a
golang.org/x/perf => github.com/golang/perf v0.0.0-20190312170614-0655857e383f
golang.org/x/sync => github.com/golang/sync v0.0.0-20190412183630-56d357773e84
golang.org/x/sys => github.com/golang/sys v0.0.0-20190416152802-12500544f89f
golang.org/x/text => github.com/golang/text v0.3.0
golang.org/x/time => github.com/golang/time v0.0.0-20190308202827-9d24e82272b4
golang.org/x/tools => github.com/golang/tools v0.0.0-20190417005754-4ca4b55e2050
golang.org/x/xerrors => github.com/golang/xerrors v0.0.0-20190410155217-1f06c39b4373
google.golang.org/api => github.com/googleapis/google-api-go-client v0.3.2
google.golang.org/appengine => github.com/golang/appengine v1.5.0
google.golang.org/genproto => github.com/google/go-genproto v0.0.0-20190415143225-d1146b9035b9
google.golang.org/grpc => github.com/grpc/grpc-go v1.20.0
gopkg.in/asn1-ber.v1 => github.com/go-asn1-ber/asn1-ber v0.0.0-20181015200546-f715ec2f112d
gopkg.in/fsnotify.v1 => github.com/Jwsonic/recinotify v0.0.0-20151201212458-7389700f1b43
gopkg.in/gorethink/gorethink.v4 => github.com/rethinkdb/rethinkdb-go v4.0.0+incompatible
gopkg.in/ini.v1 => github.com/go-ini/ini v1.42.0
gopkg.in/src-d/go-billy.v4 => github.com/src-d/go-billy v4.2.0+incompatible
gopkg.in/src-d/go-git-fixtures.v3 => github.com/src-d/go-git-fixtures v3.4.0+incompatible
gopkg.in/yaml.v2 => github.com/go-yaml/yaml v2.1.0+incompatible
k8s.io/api => github.com/kubernetes/api v0.0.0-20190416052506-9eb4726e83e4
k8s.io/apimachinery => github.com/kubernetes/apimachinery v0.0.0-20190416092415-3370b4aef5d6
k8s.io/client-go => github.com/kubernetes/client-go v11.0.0+incompatible
k8s.io/klog => github.com/simonpasquier/klog-gokit v0.1.0
k8s.io/kube-openapi => github.com/kubernetes/kube-openapi v0.0.0-20190401085232-94e1e7b7574c
k8s.io/utils => github.com/kubernetes/utils v0.0.0-20190308190857-21c4ce38f2a7
sigs.k8s.io/yaml => github.com/kubernetes-sigs/yaml v1.1.0
)

粘贴到项目对应的go.mod文件中,执行

$ go mod tidy

参考

  1. https://github.com/juju/errors
  2. https://github.com/pkg/errors

Go编译时变量

支持每次打包都动态更新版本号,编译时间之类的变量

-X importpath.name=value 
Set the value of the string variable in importpath named name to value.
Note that before Go 1.5 this option took two separate arguments.
Now it takes one argument split on the first = sign.
package main

import (
"fmt"
)

var buildstamp = ""

func main() {
fmt.Printf("Build Time : %s\n", buildstamp)
}
$export flags="-X main.buildstamp=`date -u '+%Y-%m-%d_%I:%M:%S%p'`"
$go build -ldflags "$flags" -o m m.go
$./m

参考

  1. https://golang.org/cmd/link/

Vagrant踩坑

Vagrant是用来管理虚拟机的,如VirtualBox、VMware、AWS等,主要好处是可以提供一个可配置、可移植和复用的软件环境,可以使用shell、chef、puppet等工具部署。

安装vagrant/virtualbox

由于apt install安装的版本太老, 所以从官网下载vagrant_2.2.5_x86_64.deb

king@king:~/data/vagrantbox$ vagrant up --provider=virtualbox
The provider 'virtualbox' that was requested to back the machine
'default' is reporting that it isn't usable on this system. The
reason is shown below:

Vagrant has detected that you have a version of VirtualBox installed
that is not supported by this version of Vagrant. Please install one of
the supported versions listed below to use Vagrant:

4.0, 4.1, 4.2, 4.3, 5.0, 5.1

A Vagrant update may also be available that adds support for the version
you specified. Please check www.vagrantup.com/downloads.html to download
the latest version.

添加box

$ vagrant box add ubuntu/bionic https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/bionic/current/bionic-server-cloudimg-amd64-vagrant.box
==> box: Box file was not detected as metadata. Adding it directly...
==> box: Adding box 'ubuntu/bionic' (v0) for provider:
box: Downloading: https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/bionic/current/bionic-server-cloudimg-amd64-vagrant.box
==> box: Successfully added box 'ubuntu/bionic' (v0) for 'virtualbox'!

更新

$ vagrant box update

启动

$ mkdir vagrantbox
$ cd vagrantbox/
$ vagrant init ubuntu/bionic
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.
$ ls
Vagrantfile
$ vagrant up --provider=virtualbox
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/bionic'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'ubuntu/bionic' version '20190729' is up to date...
==> default: Setting the name of the VM: vagrantbox_default_1564392888222_56525
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
default: Adapter 1: nat
==> default: Forwarding ports...
default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
default: SSH address: 127.0.0.1:2222
default: SSH username: vagrant
default: SSH auth method: private key
default:
default: Vagrant insecure key detected. Vagrant will automatically replace
default: this with a newly generated keypair for better security.
default:
default: Inserting generated public key within guest...
default: Removing insecure key from the guest if it's present...
default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
default: The guest additions on this VM do not match the installed version of
default: VirtualBox! In most cases this is fine, but in rare cases it can
default: prevent things such as shared folders from working properly. If you see
default: shared folder errors, please make sure the guest additions within the
default: virtual machine match the version of VirtualBox you have installed on
default: your host and reload your VM.
default:
default: Guest Additions Version: 5.2.22
default: VirtualBox Version: 6.0
==> default: Mounting shared folders...
default: /vagrant => /home/king/data/vagrantbox

查看进程

$ ps aufx | grep virtualbox
king \_ grep --color=auto virtualbox
king \_ /usr/lib/virtualbox/VBoxXPCOMIPCD
king \_ /usr/lib/virtualbox/VBoxSVC --auto-shutdown
king \_ /usr/lib/virtualbox/VBoxHeadless --comment vagrantbox_default_1564392888222_56525 --startvm 315ba5d9-fb4c-40ca-acf4-97dc75db70c7 --vrde config

进入shell

$ vagrant ssh
Welcome to Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-56-generic x86_64)

* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage

vagrant@ubuntu:~$ pwd
/home/vagrant
vagrant@ubuntu:~$ logout
Connection to 127.0.0.1 closed.

关机&销毁

$ vagrant halt
==> default: Attempting graceful shutdown of VM...
$ vagrant destroy
default: Are you sure you want to destroy the 'default' VM? [y/N] y
==> default: Destroying VM and associated drives...

参考

使用landslide生成幻灯片

官网 https://github.com/adamzap/landslide

安装

pip install landslide

从源码安装

$ git clone https://github.com/adamzap/landslide.git
$ cd landslide
$ python setup.py build
$ sudo python setup.py install

快捷键

  • Press h to toggle display of help
  • Press left arrow and right arrow to navigate
  • Press t to toggle a table of contents for your presentation. Slide titles are links
  • Press ESC to display the presentation overview (Exposé)
  • Press n to toggle slide number visibility
  • Press b to toggle screen blanking
  • Press c to toggle current slide context (previous and next slides)
  • Press e to make slides filling the whole available space within the document body
  • Press S to toggle display of link to the source file for each slide
  • Press ‘2’ to toggle notes in your slides (specify with the .notes macro)
  • Press ‘3’ to toggle pseudo-3D display (experimental)
  • Browser zooming is supported

使用

landslide a.md -i -o > name_you_like.html

sample

# Landslide

---

# Overview

Generate HTML5 slideshows from markdown, ReST, or textile.

![python](http://i.imgur.com/bc2xk.png)

Landslide is primarily written in Python, but it's themes use:

- HTML5
- Javascript
- CSS

---

# Code Sample

Landslide supports code snippets

!python
def log(self, message, level='notice'):
if self.logger and not callable(self.logger):
raise ValueError(u"Invalid logger set, must be a callable")

if self.verbose and self.logger:
self.logger(message, level)

Python安装Git库

依赖特定的版本

例如requirements.txt中

# ssh方式
git+ssh://git@github.com/qjw/flask-swagger.git@v1.0.1
# https方式
git+https://github.com/qjw/flask-swagger.git@@v1.0.1
# 本地文件系统
git+file:///home/king/code/flask-swagger/

或者

pip install git+https://github.com/qjw/flask-swagger.git@v1.0.1

安装之后,源码在目录venv/lib/python3.5/site-packages/flask_swagger

@后面可以是tag/branch甚至commit.

这种方案的不足是: 每次提交修改都得打tag,若是提交频繁会相当麻烦且容易出错,

如果改动之后,不重新打tag,会受到cache的影响而不能即使更新

mkdir -p pip-cache
pip install --cache-dir pip-cache -r requirements.txt

调试

在开发期间,可以使用文件系统的方式不经过外网安装

频繁的改动

前面打tag的方式,适合那些不经常变化的基础库,对于频繁有更新的项目,可以考虑使用editable packages方式安装

# 指定版本(tag)
-e git+ssh://git@github.com/qjw/flask-swagger.git@v1.0.1#flask-swagger
# 直接从master
-e git+ssh://git@github.com/qjw/flask-swagger.git#egg=flask-swagger
# 从本地目录引用(调试)
-e git+file:///home/king/code/flask-swagger#egg=flask-swagger

和前面的方式相比,多了参数-e,以及后面额egg hash,后者指定了源码存放的目录,具体的路径在venv/src/flask-swagger.

已知问题

若直接使用master或其他分支,会产生历史版本不一致的问题

比如我重新编译一个历史版本,会因为依赖变化导致不一致的行为

每一次产生一个branche是不现实的,用tag相对较好.代码管理是一个受权限控制的行为,那么这个tag由谁来打就是个问题

  1. 如果开发者自行打tag,那git tags会各种乱和冲突
  2. 如果管理员来做,那么开发效率有严重影响(在联调过程中,tag必须准备好,可是改个bug又必须重新tag)

直接使用commit id也是一种可行的办法,不过这种方式非常的不优雅

Golang defer的坑,以及函数执行顺序差异

C/C++函数结果作为参数,会从右到左

#include <cstdio>

class B{
public:
int f(int i){
printf("B %d\n",i);
return i;
}
};

class A{
public:
B f(int i){
printf("A %d\n",i);
B b;
return b;
}
};

void f(int a,int b){
printf("C %d %d\n",a,b);
}


int main(){
B b;
A a;
// 从右到左执行
f(a.f(1).f(2),b.f(3));
return 0;
}
B 3
A 1
B 2
C 2 3

golang变成了从左到右

package main

import "fmt"

type B struct{}

func (*B) f(p int) int {
fmt.Printf("B %d\n", p)
return 0
}

type A struct{}

func (*A) f(p int) *B {
fmt.Printf("A %d\n", p)
return &B{}
}

func dd(*B, int) {
fmt.Println("C")
}

func main() {
a := &A{}
b := &B{}
// 在defer之前,会计算到保留最后一个函数,也就是a.f(1)会先执行
defer a.f(1).f(2)
// 从左到右执行
defer dd(a.f(3), b.f(4))
// 匿名函数,defer统一执行
defer func() {
a.f(5).f(6)
}()
fmt.Println("main")
}
A 1
A 3
B 4
main
A 5
B 6
C
B 2

腾讯Tars初探

官网

  1. https://github.com/Tencent/Tars
  2. https://github.com/tars-node

本文重点参考

  1. https://github.com/Tencent/Tars/blob/master/Install.md
  2. https://github.com/Tencent/Tars/blob/master/docs/tars_cpp_quickstart.md

环境准备

下载代码、apt-get安装依赖

git clone git@github.com:Tencent/Tars.git
apt-get install flex bison libmysqlclient-dev

安装第三方库

${TARS_ROOT}/Tars/cpp/thirdparty/thirdparty.sh

实际就下载一个rapidjson

king@king:~/Tars/cpp/thirdparty$ tree -L 2
.
├── rapidjson
│   ├── appveyor.yml
│   ├── bin
│   ├── CHANGELOG.md
│   ├── CMakeLists.txt
│   ├── CMakeModules
│   ├── contrib
│   ├── doc
│   ├── docker
│   ├── example
│   ├── include
│   ├── include_dirs.js
│   ├── library.json
│   ├── license.txt
│   ├── package.json
│   ├── rapidjson.autopkg
│   ├── RapidJSONConfig.cmake.in
│   ├── RapidJSONConfigVersion.cmake.in
│   ├── RapidJSON.pc.in
│   ├── readme.md
│   ├── readme.zh-cn.md
│   ├── test
│   ├── thirdparty
│   └── travis-doxygen.sh
└── thirdparty.sh

编译核心组件/开发库

cd ${TARS_ROOT}/Tars/cpp/build
chmod u+x build.sh
./build.sh all

需要重来,则

./build.sh cleanall

由于ubuntu的mysql库路径和tars默认配置不一致,所以需要在编译前修改CMAKE配置,${TARS_ROOT}/Tars/cpp/build/CmakeLists.txt

set(MYSQL_DIR_INC "/usr/include/mysql")
set(MYSQL_DIR_LIB "/usr/lib/x86_64-linux-gnu/")

安装开发库

编译成功之后

cd /usr/local
sudo mkdir tars
sudo chown king:king ./tars/ # king酌情修改

cd ${TARS_ROOT}/Tars/cpp/build
./build.sh install

文件内容如下,主要用于Cpp开发(头文件/库)

king@king:/usr/local/tars$ tree -L 3
.
└── cpp
├── include
│   ├── jmem
│   ├── promise
│   ├── servant
│   ├── tup
│   └── util
├── lib
│   ├── libtarsparse.a
│   ├── libtarsservant.a
│   └── libtarsutil.a
├── makefile
│   └── makefile.tars
├── script
│   ├── create_http_server.sh
│   ├── create_tars_server.sh
│   ├── demo
│   └── http_demo
└── tools
├── tars2android
├── tars2c
├── tars2cpp
├── tars2cs
├── tars2node
├── tars2oc
├── tars2php
└── tars2python

安装基础服务

cd ${TARS_ROOT}/Tars/cpp/build
make framework-tar

会生成一个framework.tgz

cd /usr/local/app
sudo mkdir tars
sudo chown king:king ./tars/ # king酌情修改
cd ${TARS_ROOT}/Tars/cpp/build
cp ./framework.tgz /usr/local/app/tars/
cd /usr/local/app/tars
tar xzfv framework.tgz

内容如下

king@king:/usr/local/app/tars$ tree -L 2
.
├── framework.tgz
├── tarsAdminRegistry
│   ├── bin
│   ├── conf
│   └── util
├── tarsconfig
│   ├── bin
│   ├── conf
│   ├── data
│   └── util
├── tars_install.sh
├── tarsnode
│   ├── bin
│   ├── conf
│   ├── data
│   ├── tmp
│   └── util
├── tarsnode_install.sh
├── tarspatch
│   ├── bin
│   ├── conf
│   ├── data
│   └── util
└── tarsregistry
├── bin
├── conf
├── data
└── util

运行核心基础服务

准备Mysql

安装之后

CREATE DATABASE IF NOT EXISTS db_tars default charset utf8 COLLATE utf8_general_ci;
CREATE DATABASE IF NOT EXISTS tars_stat default charset utf8 COLLATE utf8_general_ci;
CREATE DATABASE IF NOT EXISTS tars_property default charset utf8 COLLATE utf8_general_ci;

修改IP。这里的IP可用域名

cd ${TARS_ROOT}/Tars/cpp/framework/sql
sed -i "s/192.168.2.131/192.168.10.6/g" `grep 192.168.2.131 -rl ./*`
sed -i "s/db.tars.com/server/g" `grep db.tars.com -rl ./*`
sed -i "s/tars2015/password/g" `grep tars2015 -rl ./*`
sed -i "s/dbuser=tars/dbuser=user/g" `grep dbuser=tars -rl ./*`

创建表

king@king:/usr/local/app/tars$ mysql -uu -pp -hserver db_tars # user/password酌情修改
mysql> source ./cpp/framework/sql/db_tars.sql

若想使用默认的帐号密码

CREATE USER 'tars'@'%' IDENTIFIED BY 'tars2015';
GRANT ALL ON db_tars.* TO 'tars'@'%';
GRANT ALL ON tars_stat.* TO 'tars'@'%';
GRANT ALL ON tars_property.* TO 'tars'@'%';

修改服务配置

修改IP地址

因为个人的Mysql没有在本机,所以单独处理

注意原来为IP的地方,不能用域名,否则会bind失败(其实可以改进一下)

export your_machine_ip=192.168.10.6
sed -i "s/192.168.2.131/${your_machine_ip}/g" `grep 192.168.2.131 -rl ./*`
sed -i "s/registry.tars.com/${your_machine_ip}/g" `grep registry.tars.com -rl ./*`
sed -i "s/web.tars.com/${your_machine_ip}/g" `grep web.tars.com -rl ./*`
export your_machine_ip=server
sed -i "s/db.tars.com/${your_machine_ip}/g" `grep db.tars.com -rl ./*`

修改Mysql帐号密码

king@king:/usr/local/app/tars$ grep dbuser -rl ./* | grep ".conf"
./tarsAdminRegistry/conf/adminregistry.conf
./tarsconfig/conf/tarsconfig.conf
./tarsconfig/bin/tarsconfig
./tarsregistry/conf/tarsregistry.conf

Mysql帐号密码通过下列的配置指定

<db>
charset=utf8
dbhost=server
dbname=db_tars
dbpass=p
dbport=3306
dbuser=u
</db>

运行

Cd /usr/local/app/tars
chmod u+x tars_install.sh
# 运行基础组件(共五个进程)
./tars_install.sh
# 运行rsync
sudo ./tarspatch/util/init.sh

进程如下

/usr/local/app/tars/tarsregistry/bin/tarsregistry --config=/usr/local/app/tars/tarsregistry/conf/tarsregistry.conf
/usr/local/app/tars/tarsAdminRegistry/bin/tarsAdminRegistry --config=/usr/local/app/tars/tarsAdminRegistry/conf/adminregistry.conf
/usr/local/app/tars/tarsnode/bin/tarsnode --locator=tars.tarsregistry.QueryObj@tcp -h 192.168.10.6 -p 17890 --config=/usr/local/app/tars/tarsnode/conf/tarsnode.conf
/usr/local/app/tars/tarsconfig/bin/tarsconfig --config=/usr/local/app/tars/tarsconfig/conf/tarsconfig.conf
/usr/local/app/tars/tarspatch/bin/tarspatch --config=/usr/local/app/tars/tarspatch/conf/tarspatch.conf

运行前端

准备Java client

cd ${TARS_ROOT}/Tars/java
mvn clean install
mvn clean install -f core/client.pom.xml

# 这一步不需要,应该是用于Java开发的,类似于C++的include/lib
mvn clean install -f core/server.pom.xml

编译之后

king@king:~/Tars/java$ ls ~/.m2/repository/com/tencent/tars/tars-client/
1.4.0 maven-metadata-local.xml

编译前端

切换到目录${TARS_ROOT}/Tars/web/src/main/resources

修改 app.config.properties

# server为mysql地址
tarsweb.datasource.tars.addr=server:3306
tarsweb.datasource.tars.user=u
tarsweb.datasource.tars.pswd=p

maven打包

mvn clean package

增加/etc/hosts (注意不是127.0.0.1)

192.168.10.6 registry1.tars.com                                                    
192.168.10.6 registry2.tars.com

若找不到依赖 qq-cloud-central

king@king:~/Tars/web$ diff pom.xml pom.xml.bak 
diff pom.xml.bak pom.xml
82c82
< <groupId>qq-cloud-central</groupId>
---
> <groupId>com.tencent.tars</groupId>
84c84
< <version>1.0.3</version>
---
> <version>1.4.0</version>

resin运行

下载http://caucho.com/download/resin-4.0.56.tar.gz 解压到/usr/local/app/resin

cd ${TARS_ROOT}/Tars/web/
cp ./target/tars.war /usr/local/app/resin/webapps/

修改conf/resin.xml

<host id="" root-directory=".">
<-- <web-app id="/" root-directory="webapps/ROOT"/> -->
<web-app id="/" document-directory="webapps/tars"/>
</host>
mkdir -p /data/log/tars
/usr/local/app/resin/bin/resin.sh start

进程如下

/usr/lib/jvm/jdk1.8.0_111/bin/java -Dresin.watchdog=app-0 -Djava.util.logging.manager=com.caucho.log.LogManagerIm
usr/lib/jvm/jdk1.8.0_111/bin/java -Dresin.server=app-0 -Djava.util.logging.manager=com.caucho.log.LogManager

发布可选组件

# 其他服务
make tarsstat-tar
make tarsnotify-tar
make tarsproperty-tar
make tarslog-tar
make tarsquerystat-tar
make tarsqueryproperty-tar

安装framework之后,tarsnotify会跑步起来,需要将上面步骤发布的tarsnotify-tar发布上去

接下来可以将剩余的5个也发布上去

Cpp Hello world

生成server

/usr/local/tars/cpp/script/create_tars_server.sh TestApp HelloServer Hello

在Ubuntu有报错,rename不了,参见文末[问题]集锦

生成接口头文件

/usr/local/tars/cpp/tools/tars2cpp Hello.tars
make
make tar

make tar 会生成.tgz包,用于上传发布到tars平台

可以按照tars_cpp_quickstart 增加一个带参数的接口

客户端

创建目录[/home/tarsproto/],必须这个,有点恶心,在后回到刚刚服务器源码的目录,例如[/usr/local/tars/TestApp/HelloServer]

make release

king@king:/home/tarsproto/TestApp/HelloServer$ tree -L 2
.
├── Hello.h
├── HelloServer.mk
├── Hello.tars
└── makefile

0 directories, 4 files

这些内容用于客户端引用

按照步骤继续添加可执行程序源码

king@king:/home/tarsproto/TestApp/TestHelloClient$ tree
.
├── main.cpp
├── makefile

0 directories, 2 files

注意修改IP地址

int main(int argc,char ** argv)
{
Communicator comm;
try
{
HelloPrx prx;
comm.stringToProxy("TestApp.HelloServer.HelloObj@tcp -h 192.168.10.6 -p 20001" , prx);
make

运行

king@king:/home/tarsproto/TestApp/TestHelloClient$ ./TestHelloClient 
2018-06-26 15:22:52|CommunicatorEpoll::run id:12838
iRet:0 sReq:hello world sRsp:hello world

问题

访问http://127.0.0.1:8080报错

NoClassDefFoundError: org/apache/log4j/spi/ThrowableInformation

修改${TARS_ROOT}/Tars/java/core/client.pom.xml,增加下列依赖

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>

mysql时间搓错误

Error updating database. Cause: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Incorrect datetime value: ‘0000:00:00 00:00:00’ for column ‘patch_time’ at row 1↵### The error may involve defaultParameterMap

/etc/mysql/mysql.conf.d/mysqld.cnf 去掉NO_ZERO_DATE选项

sql_mode = "ONLY_FULL_GROUP_BY"

文件发布上传文件失败

检查文件/目录权限

tarsnotify inactive

需要手动发布 tarsnotify-tar(另外还有五个可选组件)

生成hello world失败

Bareword "DemoServer" not allowed while "strict subs" in use at

参考 http://haoningabc.iteye.com/blog/1688936

#rename "DemoServer" "$SERVER" $SRC_FILE  
rename "s/DemoServer/$SERVER/" $SRC_FILE  
#rename "DemoServant" "$SERVANT" $SRC_FILE  
rename "s/DemoServant/$SERVANT/" $SRC_FILE

编译Hello world报错

/usr/include/c++/5/bits/c++0x_warning.h:32:2: error: #error This file requires compiler

修改当前目录makeifle,TARS2CPP_FLAG增加c11支持

TARS2CPP_FLAG:= -std=c++11

GO Oauth2/OIDC客户端/服务器

  1. 服务器参考https://github.com/RangelReale/osin
  2. 客户端参考https://github.com/golang/oauth2
  3. OIDC服务器参考https://github.com/coreos/dex
  4. OIDC客户端参考https://github.com/coreos/go-oidc

OAuth2服务器

package main

import (
"fmt"
"net/http"

"github.com/RangelReale/osin"
"github.com/RangelReale/osin/example"
)

func handleLoginPage(ar *osin.AuthorizeRequest, w http.ResponseWriter, r *http.Request) bool {
r.ParseForm()
if r.Method == "POST" {
return true
}

html := `<html><body>
LOGIN %s<br/>
<form action="/authorize?%s" method="POST">
<input type="submit"/>
</form>
</body></html>`
w.Write([]byte(fmt.Sprintf(html, ar.Client.GetId(), r.URL.RawQuery)))
return false
}

func newTestStorage() *example.TestStorage {
ts := example.NewTestStorage()
c, _ := ts.GetClient("1234")
tc := c.(*osin.DefaultClient)
tc.RedirectUri = "http://qjw.p.kimq.cn/callback"
return ts
}

func main() {
cfg := osin.NewServerConfig()
cfg.AllowGetAccessRequest = true
cfg.AllowClientSecretInParams = true

server := osin.NewServer(cfg, newTestStorage())

// Authorization code endpoint
http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()

if ar := server.HandleAuthorizeRequest(resp, r); ar != nil {
if !handleLoginPage(ar, w, r) {
return
}
ar.Authorized = true
server.FinishAuthorizeRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
})

// Access token endpoint
http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()

if ar := server.HandleAccessRequest(resp, r); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, r, ar)
}

if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}

osin.OutputJSON(resp, w, r)
})

// Information endpoint
http.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()

if ir := server.HandleInfoRequest(resp, r); ir != nil {
server.FinishInfoRequest(resp, r, ir)
}
osin.OutputJSON(resp, w, r)
})

http.ListenAndServe(":14000", nil)
}

OAuth2客户端

package main

import (
"fmt"
"io/ioutil"
"net/http"

"golang.org/x/oauth2"
)

const htmlIndex = `<html><body>
<a href="/login">Log in with oauth2</a>
</body></html>
`

var infoUrl = "http://localhost:14000/info"
var endpotin = oauth2.Endpoint{
AuthURL: "http://localhost:14000/authorize",
TokenURL: "http://localhost:14000/token",
}

var oauthConfig = &oauth2.Config{
ClientID: "1234",
ClientSecret: "aabbccdd",
RedirectURL: "http://qjw.p.kimq.cn/callback",
Scopes: []string{"api"},
Endpoint: endpotin,
}

const oauthStateString = "random"

func main() {
http.HandleFunc("/", handleMain)
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/callback", handleCallback)
fmt.Println(http.ListenAndServe(":8000", nil))
}

func handleMain(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, htmlIndex)
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
url := oauthConfig.AuthCodeURL(oauthStateString)
fmt.Println(url)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func handleCallback(w http.ResponseWriter, r *http.Request) {
state := r.FormValue("state")
if state != oauthStateString {
fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
fmt.Println(state)

code := r.FormValue("code")
fmt.Println(code)
token, err := oauthConfig.Exchange(oauth2.NoContext, code)
fmt.Println(token)
if err != nil {
fmt.Println("Code exchange failed with '%s'\n", err)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}

client := &http.Client{}
req, _ := http.NewRequest("GET", infoUrl, nil)
req.Header.Set("Authorization", "bearer "+token.AccessToken)
response, err := client.Do(req)
if err == nil {
defer response.Body.Close()
contents, _ := ioutil.ReadAll(response.Body)
fmt.Fprintf(w, "Content: %s\n", contents)
} else {
fmt.Fprintf(w, "Content: %s\n", err.Error())
}
}

Gitlab OAuth客户端

package main

import (
"fmt"
"io/ioutil"
"net/http"

"golang.org/x/oauth2"
)

const htmlIndex = `<html><body>
<a href="/login">Log in with oauth2</a>
</body></html>
`

//var infoUrl = "http://localhost:14000/info"
//var endpotin = oauth2.Endpoint{
// AuthURL: "http://localhost:14000/authorize",
// TokenURL: "http://localhost:14000/token",
//}
//var oauthConfig = &oauth2.Config{
// ClientID: "1234",
// ClientSecret: "aabbccdd",
// RedirectURL: "http://qjw.p.kimq.cn/callback",
// Scopes: []string{"api"},
// Endpoint: endpotin,
//}

var domain = "https://gitlab.example.com"
var infoUrl = domain + "/api/v4/users"
var endpotin = oauth2.Endpoint{
AuthURL: domain + "/oauth/authorize",
TokenURL: domain + "/oauth/token",
}
var oauthConfig = &oauth2.Config{
ClientID: "4f56sad4f56sa4df65safd",
ClientSecret: "7f8dasf46sa54f56s4df65",
RedirectURL: "http://qjw.p.kimq.cn/callback",
Scopes: []string{"api"},
Endpoint: endpotin,
}

const oauthStateString = "random"

func main() {
http.HandleFunc("/", handleMain)
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/callback", handleCallback)
fmt.Println(http.ListenAndServe(":8000", nil))
}

func handleMain(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, htmlIndex)
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
url := oauthConfig.AuthCodeURL(oauthStateString)
fmt.Println(url)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func handleCallback(w http.ResponseWriter, r *http.Request) {
state := r.FormValue("state")
if state != oauthStateString {
fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
fmt.Println(state)

code := r.FormValue("code")
fmt.Println(code)
token, err := oauthConfig.Exchange(oauth2.NoContext, code)
fmt.Println(token)
if err != nil {
fmt.Println("Code exchange failed with '%s'\n", err)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}

client := &http.Client{}
req, _ := http.NewRequest("GET", infoUrl, nil)
req.Header.Set("Authorization", "bearer "+token.AccessToken)
response, err := client.Do(req)
if err == nil {
defer response.Body.Close()
contents, _ := ioutil.ReadAll(response.Body)
fmt.Fprintf(w, "Content: %s\n", contents)
} else {
fmt.Fprintf(w, "Content: %s\n", err.Error())
}
}

OIDC服务器

$ go get github.com/coreos/dex
$ cd $GOPATH/src/github.com/coreos/dex
$ make

修改examples/config-dev.yml

staticClients:
- id: example-app
redirectURIs:
- 'http://qjw.p.kimq.cn/callback'
name: 'Example App'
secret: ZXhhbXBsZS1hcHAtc2VjcmV0

./bin/dex serve examples/config-dev.yaml

OIDC客户端

package main

import (
"encoding/json"
"log"
"net/http"

oidc "github.com/coreos/go-oidc"

"golang.org/x/net/context"
"golang.org/x/oauth2"
)

var domain = "http://127.0.0.1:5556/dex"
var (
clientID = "example-app"
clientSecret = "ZXhhbXBsZS1hcHAtc2VjcmV0"
)

func main() {
ctx := context.Background()

provider, err := oidc.NewProvider(ctx, domain)
if err != nil {
log.Fatal(err)
}
oidcConfig := &oidc.Config{
ClientID: clientID,
}
verifier := provider.Verifier(oidcConfig)

config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: provider.Endpoint(),
RedirectURL: "http://qjw.p.kimq.cn/callback",
Scopes: []string{oidc.ScopeOpenID},
}

state := "foobar" // Don't do this in production.

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, config.AuthCodeURL(state), http.StatusFound)
})

http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("state") != state {
http.Error(w, "state did not match", http.StatusBadRequest)
return
}

oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
return
}
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}

oauth2Token.AccessToken = "*REDACTED*"

resp := struct {
OAuth2Token *oauth2.Token
IDTokenClaims *json.RawMessage // ID Token payload is just JSON.
}{oauth2Token, new(json.RawMessage)}

if err := idToken.Claims(&resp.IDTokenClaims); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data, err := json.MarshalIndent(resp, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(data)
})

log.Fatal(http.ListenAndServe("127.0.0.1:8000", nil))
}

Dex使用Gitlab

Dex默认提供了几种不需要额外配置的认证方式,参见配置 config-dev.yaml。代码实现参见github.com/coreos/dex/storage/static.go

staticClients:
- id: example-app
redirectURIs:
- 'http://qjw.p.kimq.cn/callback'
name: 'Example App'
secret: ZXhhbXBsZS1hcHAtc2VjcmV0

connectors:
- type: mockCallback
id: mock
name: Example

# Let dex keep a list of passwords which can be used to login to dex.
enablePasswordDB: true

# A static list of passwords to login the end user. By identifying here, dex
# won't look in its underlying storage for passwords.
#
# If this option isn't chosen users may be added through the gRPC API.
staticPasswords:
- email: "admin@example.com"
# bcrypt hash of the string "password"
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: "admin"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"

为了支持Gitlab,需要

  1. 准备gitlab,可以使用gitlab.com,或者自行搭建的私有仓库,用Docker运行Gitlab非常容易,参见http://blog.kimq.cn/2017/06/19/gitlab-env/
  2. Gitlab新建applications,其中回调http://server:5556/dex/callback假设dex服务器的地址是http://server:5556,下同
  3. 修改dex配置,假设自行搭建的gitlab服务器地址http://server:8080,注意配置baseURL,redirectURI/clientID/clientSecret和gitlab上保持一致

    connectors:
    - type: gitlab
    # Required field for connector id.
    id: gitlab
    # Required field for connector name.
    name: GitLab
    config:
    # optional, default = https://www.gitlab.com
    baseURL: http://server:8080
    # Credentials can be string literals or pulled from the environment.
    clientID: 7071eb2135f75bf1d8cabd0c48c98ee38b84e25e628e0f9164cd2e40bd99fcd8
    clientSecret: e84f364add4d221cacf36cfe4136cc1c13872ce1ba17158b5ff972221d9f3e21
    redirectURI: http://server:5556/dex/callback
  4. 给dex的客户端分配clientID/clientSecret,在Gitlab上生成的clientID/clientSecret仅仅供dex自己使用(站在gitlab角度看,dex是客户端),分配的方式比较麻烦,dex提供了grpc的接口,参见github.com/coreos/dex/Documentation/api.md,编译成功之后有一个github.com/coreos/dex/bin/grpc-client。我用了一种比较讨巧的办法先测试,写一个Web API,参见github.com/coreos/dex/server/server.gogithub.com/coreos/dex/server/handlers.go

    handleWithCORS("/token", s.handleToken)
    handleWithCORS("/test", s.test)

    // 下面的参数`id`和`secret`就是新分配的`clientID/clientSecret`
    func (s *Server) test(w http.ResponseWriter, r *http.Request) {
    s.storage.CreateClient(storage.Client{
    ID: "tt",
    Secret: "123456",
    RedirectURIs: []string{"http://qjw.p.kimq.cn/callback"},
    Name: "hehe",
    })
    w.Write([]byte("Hello, world!"))
    }
  5. 运行客户端

    package main

    import (
    "encoding/json"
    "log"
    "net/http"

    oidc "github.com/coreos/go-oidc"

    "golang.org/x/net/context"
    "golang.org/x/oauth2"
    )

    var domain = "http://server:5556/dex"
    var (
    clientID = "tt"
    clientSecret = "123456"
    )

    func main() {
    ctx := context.Background()

    provider, err := oidc.NewProvider(ctx, domain)
    if err != nil {
    log.Fatal(err)
    }
    oidcConfig := &oidc.Config{
    ClientID: clientID,
    }
    verifier := provider.Verifier(oidcConfig)

    config := oauth2.Config{
    ClientID: clientID,
    ClientSecret: clientSecret,
    Endpoint: provider.Endpoint(),
    RedirectURL: "http://qjw.p.kimq.cn/callback",
    Scopes: []string{oidc.ScopeOpenID},
    }

    state := "foobar" // Don't do this in production.

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    http.Redirect(w, r, config.AuthCodeURL(state), http.StatusFound)
    })

    http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
    if r.URL.Query().Get("state") != state {
    http.Error(w, "state did not match", http.StatusBadRequest)
    return
    }

    oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
    if err != nil {
    http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
    return
    }
    rawIDToken, ok := oauth2Token.Extra("id_token").(string)
    if !ok {
    http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
    return
    }
    idToken, err := verifier.Verify(ctx, rawIDToken)
    if err != nil {
    http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
    return
    }

    oauth2Token.AccessToken = "*REDACTED*"

    resp := struct {
    OAuth2Token *oauth2.Token
    IDTokenClaims *json.RawMessage // ID Token payload is just JSON.
    }{oauth2Token, new(json.RawMessage)}

    if err := idToken.Claims(&resp.IDTokenClaims); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
    }
    data, err := json.MarshalIndent(resp, "", " ")
    if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
    }
    w.Write(data)
    })

    log.Fatal(http.ListenAndServe("0.0.0.0:8000", nil))
    }

Connector绑定

dex并未就client和特定的connector(比如gitlab)做绑定,若connector只有一个,就直接跳转,否则列出来让用户选

加入dex同时设置了gitlab和github的connector,并且我希望这些clientid只能使用gitlab,另外一些只能使用github做授权,就无法实现。

使用plantuml画UML图

官网http://plantuml.com/

主流的工具都支持渲染

  1. intellij
  2. eclipse
  3. sublime
  4. atom
  5. notepad++
  6. vscode
  7. netbeans

活动图/流程图

@startuml
start
if (condition A) then (yes)
:Text 1;
elseif (condition B) then (yes)
:Text 2;
stop
elseif (condition C) then (yes)
:Text 3;
elseif (condition D) then (yes)
:Text 4;
else (nothing)
:Text else;
endif
stop
@enduml

状态图

@startuml
scale 350 width
[*] --> NotShooting

state NotShooting {
[*] --> Idle
Idle --> Configuring : EvConfig
Configuring --> Idle : EvConfig
}

state Configuring {
[*] --> NewValueSelection
NewValueSelection --> NewValuePreview : EvNewValue
NewValuePreview --> NewValueSelection : EvNewValueRejected
NewValuePreview --> NewValueSelection : EvNewValueSaved

state NewValuePreview {
State1 -> State2
}

}
@enduml

时序图

@startuml
actor Bob #red
' The only difference between actor
'and participant is the drawing
participant Alice
participant "I have a really\nlong name" as L #99FF99
/' You can also declare:
participant L as "I have a really\nlong name" #99FF99
'/

Alice->Bob: Authentication Request
Bob->Alice: Authentication Response
Bob->L: Log transaction
@enduml

类图

@startuml
class Foo1 {
You can use
several lines
..
as you want
and group
==
things together.
__
You can have as many groups
as you want
--
End of class
}

class User {
.. Simple Getter ..
+ getName()
+ getAddress()
.. Some setter ..
+ setName()
__ private data __
int age
-- encrypted --
String password
}

@enduml

完整查看官网http://plantuml.com/

参考

  1. https://www.jianshu.com/p/e92a52770832
  2. https://yuque.com/yuque/help/editor-puml
  3. https://blog.csdn.net/zhangjikuan/article/details/53484558

Docker编译期间传递隐私数据

所谓的隐私数据,比如一些私有的token、密码、ssh key之类的不可公开但是编译期间却需要的信息

比如在docker编译期间,需要clone一个私有git仓库的代码,一般使用ssh key或者一个access token,这个密钥仅仅在编译期有用,并且不能暴露出去

几种常见思路

  1. Dockerfile ENV variable
  2. Docker build time variables

留意build-arg

docker build –build-arg HTTP_PROXY=http://10.20.30.2:1234 .

这两种方式,实际都会在镜像中留下记录,也就是若获取到镜像,可以找到这些token,所以能达到目的,但并不安全

dockito/vault

dockito/vault的思路很简单,在同一个docker环境下运行一个镜像,在里面运行一个http server。打包的docker镜像,则通过http协议去下载ssh key或者token,用完再删除。

这里的关键问题是如何找到服务器的地址

考虑到默认配置下,同一个docker环境下的容器共享同一个ip内网,所以在目标容器中获取默认网关IP,同时dockito/vault绑定到这个IP即可实现

测试

先打一个基础镜像,避免每次测试都要去apt-get和curl

file:dockerfile.base,你可以使用其他镜像,比如Alpine Linux

FROM ubuntu:14.04

RUN apt-get update && apt-get install -y curl git && \
curl -L https://raw.githubusercontent.com/dockito/vault/master/ONVAULT > /usr/bin/ONVAULT && \
chmod +x /usr/bin/ONVAULT

docker build -t base:0.1 . -f Dockerfile.base

运行vault

king@king:~/$ docker run --rm -it  -p 172.17.0.1:14242:3000 -v ~/.ssh:/vault/.ssh dockito/vault

> dockito-vault@1.0.0 start /usr/src/app
> node index.js

Service started on port 3000

或者调试模式

king@king:~/$ docker run --rm -it  -p 172.17.0.1:14242:3000 -v ~/.ssh:/vault/.ssh dockito/vault /bin/sh
/usr/src/app # node index
Service started on port 3000

在宿主主机访问下面地址确认,参见https://github.com/dockito/vault/blob/master/index.js

  1. http://172.17.0.1:14242/_ping
  2. http://172.17.0.1:14242/ONVAULT
  3. http://172.17.0.1:14242/ssh.tgz

目标镜像

FROM base:0.1
RUN ONVAULT git clone git@gitlab.kimq.cn:kingqiu/flask-swagger.git
king@king:~/$ docker build -t b:0.1 .
Sending build context to Docker daemon 131 MB
Step 1/2 : FROM base:0.1
---> e8e50a0502d0
Step 2/2 : RUN ONVAULT git clone git@gitlab.kimq.cn:kingqiu/flask-swagger.git
---> Running in 2747e4ef93b9
[Dockito Vault] Downloading private keys...
[Dockito Vault] Using ssh key: id_rsa
[Dockito Vault] Executing command: git clone git@gitlab.kimq.cn:kingqiu/flask-swagger.git
Cloning into 'flask-swagger'...
[Dockito Vault] Removing private keys...
---> 3591a1e64c2d
Removing intermediate container 2747e4ef93b9
Successfully built 3591a1e64c2d

具体怎么获取ssh key以及销毁,在脚本https://github.com/dockito/vault/blob/master/ONVAULT

使用定制ssh key

生成key,名称务必保持id_rsa

(venv) king@king:~/$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/king/.ssh/id_rsa): id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa.
Your public key has been saved in id_rsa.pub.
The key fingerprint is:
SHA256:fXnNp2bZXa9G7EhrjqsaZS/4W9XKgGB+YN5esolUWnQ king@king
The key's randomart image is:
+---[RSA 2048]----+
| . E |
| . . |
| = o |
| = B o o o |
| = S + +.o =|
| . B * =.oo+=|
| + = o.o== +|
| o o .+oo. |
| ..+oo+... |
+----[SHA256]-----+

新建config,确保和上面步骤的id_rsa同一目录,假如目录是/home/king/tmp,留意选项StrictHostKeyChecking

Host *
StrictHostKeyChecking no

运行server

docker run --rm -it -p 172.17.0.1:14242:3000 -v ~/tmp:/vault/.ssh dockito/vault

将id_rsa.pub作为deploy key设置到gitlab/github上

然后重新build

id_rsa的key

在脚本ONVAULT,若设置了环境变量VAULT_SSH_KEY,会自动更新配置config

if [[  "$VAULT_SSH_KEY" != "id_rsa" ]]; then
# configure the ssh to any host to use this ssh key
echo -e "\nHost *\nIdentityFile ~/.ssh/$VAULT_SSH_KEY" >> ~/.ssh/config
fi

重命名

mv id_rsa gitlab && mv id_rsa.pub gitlab.pub

留意环境变量VAULT_SSH_KEY

FROM base:0.1
ENV VAULT_SSH_KEY gitlab
RUN ONVAULT git clone git@gitlab.kimq.cn:kingqiu/flask-swagger.git
king@king:~/$ ./b.sh 
Untagged: b:0.1
Deleted: sha256:60987f8c94
Sending build context to Docker daemon 131 MB
Step 1/3 : FROM base:0.1
---> e8e50a0502d0
Step 2/3 : ENV VAULT_SSH_KEY gitlab
---> Using cache
---> 8b92fa1adab3
Step 3/3 : RUN ONVAULT git clone git@gitlab.kimq.cn:kingqiu/flask-swagger.git
---> Running in 43e70f25b64e
[Dockito Vault] Downloading private keys...
[Dockito Vault] Using ssh key: gitlab
[Dockito Vault] Executing command: git clone git@gitlab.kimq.cn:kingqiu/flask-swagger.git
Cloning into 'flask-swagger'...
Warning: Permanently added 'gitlab.kimq.cn,139.224.170.165' (ECDSA) to the list of known hosts.
[Dockito Vault] Removing private keys...
---> 1400a685ef0a
Removing intermediate container 43e70f25b64e
Successfully built 1400a685ef0a

问题

[Dockito Vault] Executing command: git clone git@gitlab.kimq.cn:kingqiu/flask-swagger.git
Cloning into 'flask-swagger'...
fatal: cannot run ssh: No such file or directory
fatal: unable to fork

apk add –update –virtual openssh-client

Collecting git+ssh://git@gitlab.kimq.cn/kingqiu/flask-swagger.git (from -r requirements.txt (line 57))
Cloning ssh://git@gitlab.kimq.cn/kingqiu/flask-swagger.git to /tmp/pip-o1noeq66-build
Bad owner or permissions on /root/.ssh/config
fatal: Could not read from remote repository.

chmod 0600 config && chown whoami:id -g -n config

参考

  1. https://elasticcompute.io/2016/01/22/build-time-secrets-with-docker-containers/
  2. https://github.com/dockito/vault
  3. https://github.com/mdsol/docker-ssh-exec