FPM 应用打包神器

pkg

背景

应用程序写好了以后,需要分发给其他人,这时就需要控制启动脚本啊、配置管理啊,这些事情其实还是需要操作系统提供的rpm、deb、pkg来完成,但是要写rpm-build, dpkg-config。之前写过rpm打包过程,要有自动构建等操作,还需要对文件attr进行控制,各种细节都要写。 累都累死了。
包对安装者很方便,对写包的人太蛋疼,而这就是我们今天的主角要解决的问题。

fpm @Github

真不是php-fpm模块

fpm官方号称支持以下类型打包

  • deb
  • rpm
  • solaris
  • freebsd
  • tar
  • directories
  • Mac OS X .pkg files (osxpkg)
  • pacman (ArchLinux) packages

太赞了!我的主机是Ubuntu(deb),那这次我们的目标就是Redhat的rpm好了!

安装fpm

首先安装fpm,这货依赖ruby的gem,所以

sudo apt-get install ruby ruby-dev rubygems gcc make
sudo gem install --no-ri --no-rdoc fpm

这些都安装好了以后,我们就可以开始打包了。

打包方式

推荐直接构建文件目录的方式,因为这样很简单。简单到你怎么安排文件目录结构,fpm就忠实地从根目录开始还原。例如下面这个结构

 $ mkdir test && cd test && mkdir -p usr/bin && mkdir -p var/log/dummy
$ cd .. && tree test
.
├── usr
│   └── bin
└── var
└── log
└── dummy

执行

fpm -s dir -C test

就是把test当成根目录/ , test/var 就会变成/var

简单吧!

目标包确定

我们这次选择的是rpm,所以-t (type)就指定rpm了。

fpm -s dir -C test -t rpm

接下来要确定包的名字,例如我们这次叫hi,所以-n (name) 就是 hi

$ fpm -s dir -C test -t rpm -n hi
Created package {:path=>"hi-1.0-1.x86_64.rpm"}

执行完了以后,就自动出了一个rpm包。
看看里面有什么

rpm -qlp hi-1.0-1.x86_64.rpm
/usr/bin
/var/log/dummy

Bingo!这样就做好rpm包了。

高级用法

脚本

某些包需要执行一些脚本, 可以通过--after-install --before-install等参数指定.需要注意的是, fpm会自动读取并放在包中.

配置文件

rpm和deb都有自动配置文件管理(自动diff等功能)所以最好一开始就加上所有的配置文件. --config--files 就可以指定了. 不过,deb会默认/etc下的所有文件都是配置.

一些小tips

  • git自动生成版本号 git describe --tags
  • rpm 查询包内容 rpm -qlp *.rpm
  • deb 查询包内容 dpkg -c *.deb
  • logrotate是个好功能 fpm -d logrotate会自动更新配置

2016年终总结

工作

😂这个表情真的代表了我今年的心情,本来以为可以在Glu再多干一年的,结果4月份公司就解散了。加上之前答应老婆去深圳,于是就搬来深圳了。
刚到的那几天就开始面试,结果各个公司都问,为啥不去鹅厂试试,没想到,一面试,就真的进了鹅厂清水衙门TEG😂

6月份入的职,当时就震惊啦,朗科这边不比腾大差啊,超级海景办公室,配合着六月的阳光,那是相当的漂亮。

鹅厂大牛是多,问题也多。不过得到了离职那天再来评论评论。

博客

这里也开始长草了,不过新版的bla已经日趋完善了,希望明年能和川普一样能猛刷博客。

读书

  • 《那些古怪又让人忧心的问题》
  • 《思考的技巧》
  • 《Wireshark网络分析》
  • 《数据科学入门》
  • 《机器学习 在线课程》

自己在AI领域没有优势,今年只是积累了些基础,明年一定要好好在实战中啃下来。
Go语言方面最好能学会用汇编编写,这也是提高性能,深入学习计算机的好办法。

给程序添加Prometheus监控

Demo

最近在编写Bla的时候,发现需要统计平均响应时间,CPU和内存之类的数据,在公司已经用过了metricbeat,感觉还好,但技术栈是Java的,而且内存要求太高了,所以只好换成Golang编写Prometheus做分析数据库。

Prometheus跟Golang官方标准库expvar是属于Pull的监控类型。需要服务器去拉取采集器上面的数据,因此,daemon类的程序(比如说server)最方便的地方就是直接bind一个接口暴露数据就可以了。

代码时间

go get github.com/prometheus/client_golang/prometheus

然后在自己的启动main.go里添加

func init() {
prometheus.MustRegister(httpRequestCount)
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}
var (
httpRequestCount = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "http",
Subsystem: "request",
Name: "requests_count",
Help: "The total number of http request",
})
)

代码部分就完成了。
当你需要变更数值时,直接Inc()那个httpRequestCount就好了。

测试一下

curl http://localhost:8080/metrics
# HELP http_request_requests_count The total number of http request
# TYPE http_request_requests_count counter
http_request_requests_count 73

官方对于这些Namespace、Subsystem如何命名有详细的规定,具体的大家自己看就好了。

配置Prometheus

下载安装好Prometheus之后,将刚才我们暴露的metric接口配置进去

 - job_name: "bla"
scrape_interval: "15s"
static_configs:
- targets: ['localhost:9200']

大功告成啦~~

3种优雅的Go channel用法

写Go的人应该都听过Rob Pike的这句话

Do not communicate by sharing memory; instead, share memory by
communicating.

相信很多朋友和我一样,在实际应用中总感觉不到好处,为了用channel而用。但以我的切身体会来说,这是写代码时碰到的场景不复杂、对channel不熟悉导致的,所以希望这篇文章能给大家带来点新思路,对Golang优雅的channel有更深的认识
:)

Fan In/Out

数据的输出有时候需要做扇出/入(Fan
In/Out),但是在函数中调用常常得修改接口,而且上下游对于数据的依赖程度非常高,所以一般使用通过channel进行Fan
In/Out,这样就可以轻易实现类似于shell里的管道。

func fanIn(input1, input2 <-chan string) <-chan string {
c := make(chan string)
go func() {
for {
select {
case s := <-input1: c <- s
case s := <-input2: c <- s
}
}
}()
return c
}

同步Goroutine

两个goroutine之间同步状态,例如A goroutine需要让B goroutine退出,一般做法如下:

func main() {
g = make(chan int)
quit = make(chan bool)
go B()
for i := 0; i < 3; i++ {
g <- i
}
quit <- true // 没办法等待B的退出只能Sleep
fmt.Println("Main quit")
}
func B() {
for {
select {
case i := <-g:
fmt.Println(i + 1)
case <-quit:
fmt.Println("B quit")
return
}
}
}
/*
Output:
1
2
3
Main quit
*/

可是了main函数没办法等待B合适地退出,所以B quit
没办法打印,程序直接退出了。然而,chan是Go里的第一对象,所以可以把chan传入chan中,所以上面的代码可以把quit 定义为chan chan
bool,以此控制两个goroutine的同步

func main() {
g = make(chan int)
quit = make(chan chan bool)
go B()
for i := 0; i < 5; i++ {
g <- i
}
wait := make(chan bool)
quit <- wait
<-wait //这样就可以等待B的退出了
fmt.Println("Main Quit")
}
func B() {
for {
select {
case i := <-g:
fmt.Println(i + 1)
case c := <-quit:
c <- true
fmt.Println("B Quit")
return
}
}
}
/* Output
1
2
3
B Quit
Main Quit
*/

分布式递归调用

在现实生活中,如果你要找美国总统聊天,你会怎么做?第一步打电话给在美国的朋友,然后他们也会发动自己的关系网,再找可能认识美国总统的人,以此类推,直到找到为止。这在Kadmelia分布式系统中也是一样的,如果需要获取目标ID信息,那么就不停地查询,被查询节点就算没有相关信息,也会返回它觉得最近节点,直到找到ID或者等待超时。
好了,这个要用Go来实现怎么做呢?

func recursiveCall(ctx context.Context, id []byte, initialNodes []*node){
seen := map[string]*node{} //已见过的节点记录
request := make(chan *node, 3) //设置请求节点channel
// 输入初始节点
go func() {
for _, n := range initialNodes {
request <- n
}
}()
OUT:
for {
//循环直到找到数据
if data != nil {
return
}
// 在新的请求,超时和上层取消请求中select
select {
case n := <-request:
go func() {
// 发送新的请求
response := s.sendQuery(ctx, n, MethodFindValue, id)
select {
case <-ctx.Done():
case msg :=<-response:
seen[responseToNode(response)] = n //更新已见过的节点信息
// 加载新的节点
for _, rn := range LoadNodeInfoFromByte(msg[PayLoadStart:]) {
mu.Lock()
_, ok := seen[rn.HexID()]
mu.Unlock()
// 见过了,跳过这个节点
if ok {
continue
}
AddNode(rn)
// 将新的节点送入channel
request <- rn
}
}
}
}()
case <-time.After(500 * time.Millisecond):
break OUT // break至外层,否则仅仅是跳至loop外
case <-ctx.Done():
break OUT
}
}
return
}

这时的buffered
channel类似于一个局部queue,对需要的节点进行处理,但这段代码的精妙之处在于,这里的block操作是select的,随时可以取消,而不是要等待或者对queue的长度有认识。

你对这三种channel的用法有什么疑问,欢迎讨论╮(╯▽╰)╭

Golang与树莓派

最近买了个树莓派3b,本来是做下载机用的,但是发现在上面写Go代码,编译,其实和在一般机器上的体验是一样的。

不过树莓派本身有其他电脑没有的玩法,那就是GPIO的支持,配合Go-gpio库,就可以控制这些接口

下面是一个简单的跑马灯+CPU温度探测程序

因为没加散热片……所以温度有点高┐( ̄ヮ ̄)┌

代码如下,根据/sys下的温度文件读数值,另一个是根据负载改变闪烁的频率。

很简单,所以我就不加注释了:)至于为啥叫jurassic,因为侏罗纪公园的电网就是蓝橙指示灯,然后写代码的胖子就被吃掉了

package main
import (
"fmt"
"io/ioutil"
"strconv"
"time"
"runtime"
"github.com/stianeikeland/go-rpio"
"github.com/shirou/gopsutil/load"
)
const (
BLUE = 20
ORANGE = 21
CORE_TEMP_PATH = "/sys/class/thermal/thermal_zone0/temp"
)
func init(){
runtime.GOMAXPROCS(1)
}
func main() {
fmt.Printf("System initial...")
if rpio.Open() == nil{
fmt.Println("[OK]")
} else {
fmt.Println("[ERROR]")
}
defer rpio.Close()
orange := rpio.Pin(ORANGE)
blue := rpio.Pin(BLUE)
orange.Output()
blue.Output()
orange.Low()
blue.High()
for {
stat, err := load.Avg()
if err != nil {
fmt.Println(err)
break
}
interval := int(stat.Load1)
if stat.Load1 < 1 {
interval = 1
}
fmt.Printf("Load1:%.2f Temp:%.2f'C", stat.Load1, loadTemp())
time.Sleep(time.Millisecond * time.Duration(interval * 900))
blue.Toggle()
orange.Toggle()
fmt.Printf("\r")
}
}
func loadTemp() float64 {
b, err := ioutil.ReadFile(CORE_TEMP_PATH)
if err != nil {
return -1000
}
raw, err := strconv.ParseFloat(string(b[:len(b)-2]), 64)
if err != nil {
fmt.Println(err)
return -1001
}
return raw/100
}