shell 基础知识

todo

推荐阅读:Shell 十三问

Golang 编译技巧

编译参数

PROJECT=github.com/xxx/xxx
VERSION=$(shell git describe --tags --always)

$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags="-s -w -X ${PROJECT}/app.version=${VERSION} "
  • -installsuffix 自Go 1.10以后,你不必再使用此参数(或许更早的版本),Go的核心开发人员Ian Lance Taylor已经确认了这一点。
  • -a参数,它强制重新编译相关的包。
  • -ldflags 选项,可以向链接器传递指令
    • -s 忽略符号表和调试信息,
    • -w忽略 DWARF 符号表,通过这两个参数,可以进一步减少编译的程序的尺寸,经常用于减少 Golang 体积。
    • -X 指令可以设置程序中字符串变量的值
    • 更多的参数可以参考go link, 或者 go tool link --help(另一个有用的命令是go tool compile -help)。
  • CGO_ENABLED=0,完全静态编译,不会再依赖动态库。这一点在创建 docker 使用的二进制版本时非常有用。
  • GOOSGOARCH 是 Golang 进行交叉编译的时候指定环境的变量,具体有
    • GOOS: linux windows darwin freebsd
    • GOARCH: amd64 386 ppc64 ppc64le s390x arm arm64
    • ppc64, ppc64le, s390x, arm and arm64 只有 Linux 支持。

常见问题

如果设置CGO_ENABLED=0,并且你的代码中使用了标准库的net包的话,有可能编译好的镜像无法运行,报sh: /app: not found的错误,尽管/app这个文件实际存在,并且如果讲基础镜像换为centos或者ubuntu的话就能执行。这是一个奇怪的错误,原因在于:默认情况下net包会使用静态链接库, 比如libc,解决方案如下:

  • 设置 CGO_ENABLED=0
  • 编译是使用纯go的net: go build -tags netgo -a -v
  • 使用基础镜像加glibc(或等价库musluclibc), 比如 busybox:glibc、alpine + RUN apk add --no-cache libc6-compat
  • alpine 推荐 frolvlad/alpine-glibc 镜像,这个镜像包含了 alpine-pkg-glibc,也可以手动安装 alpine-pkg-glibc

列出了一些常用的基础镜像:

  • scratch: 空的基础镜像,最小的基础镜像
  • busybox: 带一些常用的工具,方便调试, 以及它的一些扩展busybox:glibc
  • alpine: 另一个常用的基础镜像,带包管理功能,方便下载其它依赖的包

显然。 你应该只在编译阶段使用Go的镜像,这样才能将你的镜像减小到最小。

👇情况还有没有在实际情况遇到

有的同学说了,我代码中确实必须使用CGO,因为需要依赖一些C/C++的库。目前没有对应的Go库可替代, 那么可以使用-extldflags "-static",go tool link --help介绍了extldflags的功能:

-extldflags flags
    pass flags to external linker

-static means do not link against shared libraries

自动生成版本信息

go build 包含一个 -ldflags 选项,可以向链接器传递指令。向链接器传一个 -X 指令可以设置程序中字符串变量的值。利用这个方法能够实现在编译时设置程序的版本信息。

package version

import (
	"fmt"
	"runtime"

	"github.com/spf13/cobra"
)

var (
	version    = "devel"
	buildDate  string
	commitHash string // 大小写都可以
)

var VersionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print the version number of bgo",
	Long:  `All software has versions. This is bgo's`,
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Printf(`bgo:
  version     : %s
  build date  : %s
  git hash    : %s
  go version  : %s
  go compiler : %s
  platform    : %s/%s
`, version, buildDate, commitHash,
			runtime.Version(), runtime.Compiler, runtime.GOOS, runtime.GOARCH)
	},
}

APP=bgo
PROJECT="github.com/xxx/${APP}"
VERSION=$(git describe --tags --always)
COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null)
DATE=$(date "+%Y-%m-%d") # 可以省略为 data

GOOS=linux
GOARCH=amd64

.PHONY: build
build:
    CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags "-s -w -X ${PROJECT}/cmd/version.commitHash=${COMMIT_HASH} -X '${PROJECT}/cmd/version.buildDate=`date`' -X ${PROJECT}/cmd/version.version=${VERSION}"  -o ${APP}

注意因为date输出有空格,所以${PROJECT}/cmd/version.buildDate必须使用引号括起来。

同时 -X 里面可以执行 shell 命令,例如 '${PROJECT}/cmd/version.buildDate=date' 中的 date 命令

bgo:
  version     : v0.0.1-3-gf5b6e36
  build date  : 2019年 4月13日 星期六 10时27分25秒 CST
  git hash    : f5b6e36
  go version  : go1.12.3
  go compiler : gc
  platform    : darwin/amd64

ci 自动部署到 Github Page

这个博客搭建是使用 hugo 搭建的,之前用的是 Coding.net + webhook + DaoClouddocker,但是这个方案每次发布 post 都需要1分多钟,而且为了把博客从 Coding Page 迁移到 Github Page,所以现在使用 Github + circleci.com,博客的源码放在 Github 私有仓库,通过 ci 进行生成静态页面,并且推送到公开 Github Page

  • 为了解决 Git 子仓库问题,暴力的直接的把 .git 删除,在 public 创建新的仓库
  • 需要创建新的一对秘钥ssh-keygen -m PEM -t rsa,添加到公有仓库的 Deploy keys,别忘了添加写权限
  • 需要把这个私钥添加到 circleci.com 具体项目中,项目设置里(PERMISSIONS -> SSH Permissions),把私钥添加进去,Hostname 填 github.com,然后再 ci 脚本中 add_ssh_keys 添加具体的秘钥名称
  • 为了解决 ssh 验证可以使用命令 ssh-keyscan github.com >> ~/.ssh/known_hosts
  • 由于主题需求 hugo 的 extended 版本,但是此版本需要 glibc ,所以推荐 frolvlad/alpine-glibc 镜像或者手动安装 alpine-pkg-glibc
  • 注意路径问题即可
version: 2
jobs:
  build:
    docker:
      - image: frolvlad/alpine-glibc
        environment:
          HUGO_ENV: production
          HUGO_VERSION: "0.55.1"
    working_directory: ~/project
    steps:
      - checkout
      - run: ls -al && pwd
      - add_ssh_keys:
          fingerprints:
            - "xxxxxxxxxxxxxx"
      - run: apk update && apk add --no-cache wget git ca-certificates libstdc++ openssh
      - run: wget https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz
      - run: tar xf hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz && chmod +x hugo
      - run: ls -al && pwd
      - run: git submodule add -f https://github.com/Fastbyte01/KeepIt themes/KeepIt
      - run: rm -rf .git
      - run: ./hugo --gc --minify
      - run: cd public && ls -al
      - run: ssh-keyscan github.com >> ~/.ssh/known_hosts
      - run: ./deploy.sh

workflows:
  version: 2
  main:
    jobs:
      - build
git config --global user.email xxxx
git config --global user.name xxxx

cd public

git init

git remote add origin git@github.com:xxxx/xxxx.github.io.git

#deploy
git add .
git commit -m "Site updated by ci: `date +"%Y%m%d-%H:%M:%S"` UTC+8"
git push -f origin master

ci 自动部署到 Github Release

这个 ci 的作用是,可以把打好标签的源码进行编译,然后推送到 Github Release,基本设置和上面部署到 Github Page 基本一样,只是提供一个思路,具体细节根据具体情况而定

  • run 命令可以拆解为 namecommand ,这样可以支持复杂的运行命令
  • workflows 中设置的流程是
    • 所有推送都会执行 build
    • 只有打了标签格式是 v0.1.0 的才会执行 deploy
    • 执行 deploy 会执行 build
  • 为了实现上面的策略不经要在 deploy 增加过滤策略,还要在 build 增加过滤策略,具体可查看Using Workflows to Schedule Jobs
  • make build-all 是我参照 dep hack/build-all.bash 编写的脚本,具体可以自己编写
  • github.com/tcnksm/ghr 是用来把编译好的二进制上传到 Github Release 的封装的工具,需要添加 GITHUB_TOKEN,具体使用可以看 README.md
  • GITHUB_TOKEN 生成是在 https://github.com/settings/tokens Generate new token ,具体权限只需要 Full control of private repositories 也就是 repo 下的四个权限
  • GITHUB_TOKEN 忘记只能重新生成,没有找回的地方
  • GITHUB_TOKEN 添加到具体 ci 项目设置中的 environment 即可,这样就不要写在 ci 文件中
version: 2
jobs:
  build:
    docker:
      - image: circleci/golang:latest
    working_directory: /go/src/github.com/haozibi/bego
    steps:
      - checkout
      - run: go get -u github.com/golang/dep/cmd/dep
      - run: dep ensure -v
      - run:
          name: run tests
          command: |
            go fmt ./...
            go vet ./...
            go test -v ./...
      - run: # 只是 build 一下
          name: run build
          command: |
            make build
            ls -al

  deploy:
    docker:
      - image: circleci/golang:latest
    working_directory: /go/src/github.com/haozibi/bgo
    steps:
      - checkout
      - run: go get -u github.com/golang/dep/cmd/dep
      - run: go get -u github.com/tcnksm/ghr
      - run: dep ensure -v
      - run: make build-all
      - run: ls -al && ls -al release
      - run:
          name: create release
          command: |
            tag=$(git describe --tags --always)
            echo $tag
            if [ "$tag" ]; then
              ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --replace $tag release/
            else
              echo "The commit message(s) did not indicate a major/minor/patch version."
            fi

workflows:
  version: 2
  build-deploy:
    jobs:
      - build:
          filters:  # required since `deploy` has tag filters AND requires `build`
              tags:
                only: /.*/
      - deploy:
          requires:
            - build
          filters:
            tags:
              only: /^v(\d+)\.(\d+)\.(\d+)/
            branches:
              ignore: /.*/

其他

以上两个 ci 都是在线上使用,具体细节需要根据你的

参考链接