freenas 安装 syncthing 并配置权限

freenas 安装 syncthing 并配置权限

pw userdel syncthing
pw groupdel syncthing

pw groupadd -n gdata -g 1001
pw useradd -n syncthing -u 1002 -d /vat/tem/syncthing -s /usr/sbin/nologin -g gdata

freenas本身
添加 syncthing 用户,uid为 983

执行下列命令,保证数据目录所属组为 gdata,并为所有目录增加 SGID 权限。
find /some/dir -type d -exec chgrp www {} +
find /some/dir -type d -exec chmod g+s {} +

syncthing@jails@freenas 内执行下面的命令。创建 dgata 用户组,并将 syncthing 加到组里面。

chown -R 账号名:组名 文件或目录
pw groupadd -n gdata -g 1001
pw groupmod gdata -m syncthing

新建文件所属组设置
https://blog.csdn.net/furzoom/article/details/77737344

枫竹梦 2017-08-31 10:54:29

问题场景1:在Linux进行开发Web程序,Web root目录下某些目录需要被www用户进行写操作,而开发过程中使用的用户假设为mn,新建的目录和文件默认用户/组为mn:mn,这对www用户来讲是不可写的。最简单的办法是使用chown -R mn:www xxx设置用户/组为mn:www。这样每次有新的文件都需要进行设置,十分不方便。

问题场景2:对于运维人员来讲,需要在dirA目录下新建的文件为groupA组,在dirB目录下新建的文件为groupB组。

解决办法
使用Linux下的特殊权限位即可以实现。例如对于问题场景2中的情况,将dirA目录的用户组设置为groupA,然后添加SGID权限,即:

chgrp groupA dirA/
chmod g+s dirA/

这样得到dirA的目录权限为

drwxrwsr-x

以后在dirA下新的目录具有相同的权限,且用户组为groupA;新建的文件用户组同样为groupA。

如果是已经存在的目录需要同样的设置,使用如下命令:

find /some/dir -type d -exec chgrp www {} +
find /some/dir -type d -exec chmod g+s {} +

NB: 上述命令的 {} + 表示将find查找到的所有目录添加到命令后进行执行,而不是对每一个查找到的目录执行一次。

特殊权限知识
Linux下的文件权限除了用户、组、其他的可读(r)、可写(w)、可执行(x)之外,还有三个特殊的权限位,它们是SUID、SGID、SBIT(粘滞位)。

SUID
s出现在文件所有者的x权限位上。
SUID用于可执行文件,使得该文件在执行时具有该文件所有者的权限。
对于没有可执行权限的文件设置SUID,该文件不会具体可执行权限。
chmod u+s somefile

如/usr/bin/passwd的文件。
SGID
s出现在文件所属组的x权限上。
SGID用于文件和目录。

对于目录,设置SGID,在该目录下新建的目录同样有SGID权限和所属组。
对于文件,设置SGID,使用该文件执行时具有所属组的权限。
chmod g+s somefile
SBIT
t出现在文件其他的x权限上。
SBIT用于目录。

设置了SBIT的目录,下新建的文件,只有自己和root可以进行删除。

如/tmp目录。

No comment »

golang 数组去重

需要对数组去重,一开始懒得写,搜索了下网上的实现,发现要么建立一个新的数组,要么需要多次拷贝。
由于需要处理的量比较大,性能需要尽可能的比较好,所以自己写了个,最悲观的情况下每个元素最多移动一次。

 

package main

import (
	"fmt"
	"sort"
)

func RemoveDuplicate(v []int) []int {
	//为了性能需要尽可能的减小拷贝,最悲观的情况每个元素只移动一次。
	toIndex := 0
	p := 0

	for i, _ := range v {
		// 为了实际去重结构时减小内存拷贝
		c := &v[i]

		if p == *c && i != 0 {
			// 重复内容,跳过
			continue
		}

		if i != toIndex {
			// 需要移动当前元素
			v[toIndex] = *c
		}

		toIndex++
		p = *c
	}

	return v[:toIndex]
}

func main() {
	v := []int{9, 1, 1, 9, 2, 2, 3, 3, 3, 4, 5, 6, 7, 7, 7, 7, 8}

	// 升序排序
	sort.Slice(v, func(i, j int) bool { return v[i] <= v[j] })
	//sort.Ints(v)

	// 去重
	v = RemoveDuplicate(v)

	// 打印结果
	// []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Printf("%#v", v)
}


No comment »

4线风扇温度调速+WEB显示

 

-- D6 接 ds18b20 中  左为地 右VCC
-- D7 接风扇调速(蓝线(最边上))。 4线风扇依次为:地、VCC、测速(1圈2方波)、PWN调速

ds18b20=require("ds18b20")

ds18b20.setup(6)

pwm.setup(7, 1000, 512)
pwm.start(7)

-- 温度越高转速越高
-- minT 度是起点    
minT = 20
-- maxT 度是风扇最高速度
maxT = 80

temperature = nil
speed = nil
duty = 1023

function t()
    temperature = ds18b20.readNumber(nil,ds18b20.C)
    if (temperature == nil) then
        duty = 1023
    else
        if (temperature<=minT) then
            duty = 0
        elseif (temperature>=maxT) then
            duty = 1023
        else
            duty = (temperature-minT)*1023/(maxT-minT)
        end
    end    
    pwm.setduty(7,duty)    
    print("temperature:",temperature," duty:",duty,"/1023")
    tmr.softwd(-1)
    tmr.softwd(5)
end

tmr.alarm(1,1000,tmr.ALARM_AUTO,t)

tmr.softwd(5)

wifi.setmode(wifi.STATION)
wifi.sta.config("XXX", "XXXXXXXXXX", 1)
wifi.sta.sethostname("NodeMCU")
cfg={}
cfg.ip="192.168.101.6"
cfg.netmask="255.255.255.0"
cfg.gateway="192.168.101.1"
wifi.sta.setip(cfg)

srv=net.createServer(net.TCP) 
srv:listen(80,function(conn) 
    conn:on("receive",function(conn,payload) 
        local html = string.format("HTTP/1.0 200 OK\r\n"
        .."Content-Type: text/html\r\n"
        .."Connection: Close\r\n\r\n"
        .."Temperature:%f
duty:%d/1023",temperature,duty) conn:send(html,function(sent) conn:close() end) end) end)

No comment »

ProtoThread 协程复习笔记

近一年没用 ProtoThread 了,忘得差不多了。这次搞硬件密码器重新学习了下,同时记录下来笔记,方便下次使用。

ProtoThread 是一个资源占用极少的多线程协程库,可以在51、avr等资源极其有限的硬件上面运行。

ProtoThread 有几种实现方式,这里只考虑存c switch 实现。这个实现其实就是用1byte存放当前执行到的位置,下次恢复这个线程的时候通过 switch 跳转到上次执行的位置继续执行。每个线程就只占用2byte存放当前位置(当前代码行号)即可。伪代码:

 
//线程结构体
struct pt {
  unsigned short lc;   // 当前线程执行到的位置(行号)
};
PT_THREAD(f1(struct pt *pt))
// 展开宏后的代码
// char f1(struct pt *pt)
{
    // 这里可以写每次任务切换进来都执行的代码    
    //  注意,只有静态变量才能正确的保存,非静态变量在切换协程时会丢失
    // 同样,静态变量的限制使得一个函数不能多协程同时执行,局部变量会冲突。
    static int a;

   PT_BEGIN(pt);  // 准备协程环境
    // 展开宏后的代码:
    // char PT_YIELD_FLAG = 1;
   //  switch(pt.lc) { case 0;
   // 等待至条件达成
    // 可以看到,是通过记录当前执行位置后直接退出函数来实现的让出cpu。
    // 恢复执行时通过 switch 的 case 跳转回上次执行的位置来实现的恢复执行。
    PT_WAIT_UNTIL(pt, 条件);
    // 展开后的代码:
    /*
  do {
    (pt)->lc =  __LINE__;
     case __LINE__:;
    if(!(condition)) {
      return PT_WAITING;  
    }
  } while(0)
   */
   PT_END(pt); // 结束协程环境
    // 展开后的代码:
    // };
    // PT_YIELD_FLAG = 0;
    // pt.lc=0;
    // return PT_ENDED;
}
void main()
{
    struct pt p1;    // 线程1
    struct pt p2;   // 线程2
   //初始化线程
    PT_INIT(&p1); // 其实就是 p1.lc = 0
    PT_INIT(&p2); 
   while(1)
    {
      /// 启动两个线程
      f1(&p1); 
      f1(&p2);        
    }
}

这种实现的几个特点:
• 纯c实现,无硬件依赖,方便移植
• 不能在函数内再次使用switch
• 资源占用极少,每线程仅占用2字节
• 支持阻塞操作,没有栈的切换
• 运行在单一c函数中,无法跨函数是用。弥补措施是被调用函数也写成单独的Protothread
• 线程切换时不保存局部变量,弥补措施是使用静态局部变量,造成单个函数不能多线程同时执行。另一个弥补措施是实现自己的 pt 结构,将需要保存的局部变量保存到 pt 结构里面。

附几个函数:
PT_INIT(pt) 初始化任务变量,只在初始化函数中执行一次就行
PT_BEGIN(pt) 启动任务处理,放在函数开始处
PT_END(pt) 结束任务,放在函数的最后
PT_WAIT_UNTIL(pt, condition) 等待某个条件(条件可以为时钟或其它变量,IO等)成立,否则直接退出本函数,下一次进入本 函数就直接跳到这个地方判断
PT_WAIT_WHILE(pt, cond) 和上面一个一样,只是条件取反了
PT_WAIT_THREAD(pt, thread) 等待一个子任务执行完成
PT_SPAWN(pt, child, thread) 新建一个子任务,并等待其执行完退出
PT_RESTART(pt) 重新启动某个任务执行
PT_EXIT(pt) 任务后面的部分不执行,直接退出重新执行
PT_YIELD(pt) 锁死任务
PT_YIELD_UNTIL(pt, cond) 锁死任务并在等待条件成立,恢复执行
在pt中一共定义四种线程状态,在任务函数退出到上一级函数时返回其状态
PT_WAITING 等待
PT_EXITED 退出
PT_ENDED 结束
PT_YIELDED 锁死

(下面的是定时器,该宏是 http://www.cnblogs.com/xiaowuyi/p/4319720.html 写的,用之前请在#include “pt.h” 的前面,前面啊!加上一句#define PT_USE_TIMER)

先说明一下,下面的定时不一定完全准确的,可能会有点点的误差,可能偏后。如果遇上了很烦的任务,有可能会使延时延后。但是正常情况下,直接用就好了。

如果要很精确的延时,请用delay语句或者计时器,但是,绝大多数情况下,绝大多数情况!绝大多数情况!请用下面的语句代替delay延时!这样才能把CPU让给别的任务使用。

PT_TIMER_DELAY(pt,延时毫秒数);
字面上的意思,不用多说了吧?最大值约为49.7天,估计没人会延时辣么久吧……

PT_TIMER_MICRODELAY(pt,延时微秒数);
字面上的意思,不用多说了吧?注意,最小精度与arduino的版本有关,与micros()有精度一致。

PT_TIMER_WAIT_TIMEOUT(pt,条件,毫秒数);
如果条件成立了,或者超时了,就继续运行,否则切换任务。

要用的话,请在#include “pt.h”前面加上一句 #define PT_USE_SEM

首先要创建一个信号量,这个一定是全局变量:
static struct pt_sem 信号量名;

接着请在setup()函数里面给它初始化:
PT_SEM_INIT(&信号量名,数量);
信号量名前面有个&,别忘了。数量就相当于停车场的总车位数。

然后要用啦。任务要停一辆车进去:
PT_SEM_WAIT(pt,&信号量名);
信号量名前面有个&,别忘了。一个语句只能停一辆车,土豪好多车就用多次。

任务要开一辆车出来:
PT_SEM_SIGNAL(pt,&信号量名);
信号量名前面有个&,别忘了。用一次出一辆。

参考:
• http://dunkels.com/adam/pt/
• Protothread机制文档 http://blog.csdn.net/tietao/article/details/8507455
• 一个“蝇量级” C 语言协程库 http://coolshell.cn/articles/10975.html
• ProtoThreads http://blog.csdn.net/utopiaprince/article/details/6041385

No comment »

修正中文 gitbook pdf 书籍字体大小不一的问题

第二次用了,记录下来,下次就不用在查资料了。

gitbook 网站生成的 pdf 中文字体非常难看,字体大小不一。

解决办法是手工指定中文字体,指定 pdf 文字大小。

book.json 例子:

 
{
  "gitbook": "2.x.x",
  "title": "Go语言圣经",
  "description": "中文版",
  "language": "zh",
  "structure": {
    "readme": "preface.md"
  },
  "pluginsConfig": {
    "fontSettings": {
      "theme": "white",
      "family": "msyh",
      "size": 2
    },
    "plugins": [
      "yahei",
      "katex",
      "-search"
    ]
  },
  "pdf": {
    "pageNumbers": true,
    "fontSize": 18,
    "paperSize": "a4",
    "margin": {
      "right": 30,
      "left": 30,
      "top": 30,
      "bottom": 50
    }
  }
}

例子使用的 微软雅黑 字体,默认linux下没有这个字体,需要手工将字体拷贝到 /usr/share/fonts/truetype 目录。

可以通过 travis 自动生成 pdf 文件,.travis.yml 例子是:

 
language: node_js

node_js:
  - "stable"

script:
  - echo pass

before_deploy:
  - sudo apt-get update -qq
  # install Chinese fonts
  - sudo apt-get install -y fonts-arphic-gbsn00lp golang
  # Install Gitbook
  - npm install gitbook-cli -g
  - npm install svgexport -g
  - npm install gitbook-plugin-yahei
  # Clone the repository
  - sudo -v && wget --no-check-certificate -nv -O- https://raw.githubusercontent.com/kovidgoyal/calibre/master/setup/linux-installer.py | sudo python -c "import sys; main=lambda:sys.stderr.write('Download failed\n'); exec(sys.stdin.read()); main()"

  - sudo cp tools/*.ttc /usr/share/fonts/truetype

  - go run zh2tw.go . .md$ tw2zh

  - gitbook install
  - gitbook build ./ --format=json
  - gitbook build ./
  - gitbook pdf ./ ./gopl-ch.pdf
  - gitbook epub ./ ./gopl-ch.epub
  - gitbook mobi ./ ./gopl-ch.mobi


deploy:
  provider: releases
  api_key: $CI_USER_TOKEN
  skip_cleanup: true
  file:
    - gopl-ch.pdf
    - gopl-ch.epub
    - gopl-ch.mobi
  on:
    tags: true
    all_branches: true

这个例子会在每次 git tag 时自动生成 pdf 文件,并发布到 github 。需要将 github token 设在到 CI_USER_TOKEN 环境变量。

Comments (2) »

Docker 小抄

之前学习docker时整理的,现在有的命令忘记了。打印出来,方便随时查找。

web 版本格式没了,[PDF]Docker 小抄 有正确的格式。

镜像部分

docker pull ubuntu:14.04
获得 ubuntu 标签为 14.04的镜像,如果省略14.04则下载 latest 标签的镜像,ubuntu默认为最新的长期支持版本。相当于:docker pull registry.hub.docker.com/ubuntu:14.04 。

docker images
显示本地已有镜像。镜像id表示了唯一的镜像,有些的镜像可能存在多个 TAG ,但是id一致表示实际是一个镜像。

docker commit -m “提交说明” -a “提交用户” 容器id 用户名/仓库名:TAG
提交镜像,只是提交到本地,并没有上传到 docker hub 。

sudo cat ubuntu-14.04-x86_64-minimal.tar.gz |docker import – ubuntu:14.04
从本地导入镜像。源可以是 openvz 模板,地址:http://openvz.org/Download/templates/precreated

docker push ouruser/sinatra
上传镜像到 docker hub

docker save -o ubuntu14.04.tar ubuntu:14.04
导出镜像到文件

docker load –input ubuntu14.04.tar 或 docker load < ubuntu14.04.tar 导入镜像,这将导入镜像及相关元数据(包括标签等信息)。 docker rmi aaaaa/bbbb 删除镜像,注意 docker rm 是删除容器。无法删除使用中的镜像。 docker rmi $(docker images -q -f "dangling=true") 移除未打标签的中间镜像。 容器 docker run ubuntu:14.04 /bin/echo 'hello word' 使用 ubuntu:14.04 镜像启动一个新的容器并执行 /bin/echo 'hello word' 命令。 默认容器停止后还会继续保留,可以通过 start 命令重新启动,可以通过 --rm 参数实现容器停止自动删除的功能。--restart=always选项用来保证Docker守护进程在容器出错或者重启后自动启动容器。 docker run -t -i ubuntu:14.04 /bin/bash 启动容器并执行 /bin/bash 命令。 -t 是分配伪终端并绑定到容器的标准输入上面。-i 则是保持标准输入打开状态。 docker start 容器id 启动已停止的容器,将继续执行创建时指定的命令。通过增加 -i 参数可以进入交互模式。 docker stop 容器id 停止容器。并不会丢失对容器所做的修改!!通过 start 重新启动容器后可以发现之前的修改都保存着! docker restart 容器id 重启容器。同样不会丢失所做的修改! docker run -d 镜像名称 命令 后台启动容器,就是不要将标准输出在当前终端下。注意:容器是否会长期运行是和指定的命令有关,而和 -d 参数没有关系。 docker log 容器id 查询容器日志,就是标准输出记录。 docker attach 容器id 附加到指定容器,个人理解就是将容器标准输出连接到当前终端,将当前中断的标准输入连接到容器。注意,多个附加操作是显示一样的内容。实际操作容器时建议通过新命令 docker exec 大体。退出,一定不要用ctrl+c,那样就是让docker容器停止了。要用如下快捷键:先按,ctrl+p再按,ctrl+q 。使用CTRL-c或CTRL-d退出容器,将向容器发送SIGKILL,导致容器停止。使用 CTRL-\退出容器时,将会输出docker客户端的堆栈信息。 docker run -t -i → can be detached with ^P^Q and reattached with docker attach docker run -i → cannot be detached with ^P^Q; will disrupt stdin docker run → cannot be detached with ^P^Q; can SIGKILL client; can reattach with docker attach docker exec 容器id 命令 在指定的容器内执行命令。执行/bin/bash命令需要通过 -ti 选项打开伪终端及标准输入。已经关闭的容器无法执行命令。 nsenter 老的在指定容器执行命令的方案。 docker export 容器id > ubuntu.tar
导出容器快照到文件。

cat ubuntu.tar | sudo docker import -test/ubuntu:v1.0
导入容器快照为镜像。容器快照不保存历史纪录及元数据,体积小;load 导入的镜像储存包含历史纪录及元数据(标签等),体积比较大。

docker import http://www.aaa.com/bbb.tgz
通过网络导入容器快照。

docker rm 容器id
删除容器。-v 参数表示同时删除数据卷,否则即使容器被删除数据卷也会永久保留。

docker rm -v $(docker ps -a -q)
删除所有未运行的容器,-v 参数使得会同时删除数据卷。

docker inspect 容器id
审计容器,显示容器的详细信息。

数据管理

sudo docker run -ti -v /user_data ubuntu /bin/bash
启动一个容器,并且挂载了一个 /user_data 的数据卷。数据卷绕过了 UFS ,对数据卷的更新不会影响镜像,默认会一直存在,不会随着容器的删除而删除。实际实现是见一个目录挂载到了容器指定的目录,这里是 /user_data 。未指定数据卷源时源是在主机的 /var/lib/docker/volumes 目录下,docker inspect 容器id 输出的容器详细信息里面有数据卷的详细信息。
“Volumes”: {
“/udata”: “/var/lib/docker/volumes/1328a35ef12eb9cfa97210f567a13d70ccf157856c2d79f922a858ef4da1fad8/_data”
},
“VolumesRW”: {
“/udata”: true
},
可以看到 /user_data 数据卷实际是主机的/var/lib/docker/volumes/1328a35ef12eb9cfa97210f567a13d70ccf157856c2d79f922a858ef4da1fad8/_data目录,对这个目录的修改容器的/user_data 会实时的反应出来。
不指定数据卷源时会自动创建一个新的目录作为数据卷的源,默认每个容器的数据卷不相关。

docker run -ti -v /user_data:~/user_data:ro ubuntu /bin/bash
使用主机的 ~/user_data 目录作为源为容器创建一个路径是 /user_data 的只读数据卷,ro 表示只读。
也可以挂载单个文件作为数据卷,但是 vi 等文本编辑工具会造成文件 inode 改变,会使得docker报告错误。

docker rm -v 容器id
删除容器并删除数据卷。资料显示目前 docker 默认删除容器时不会删除容器卷,同时不存在删除无主地数据卷的功能,是个大坑…

docker run -d -v /dbdata –name dbdata training/postgres echo Data-only container for postgres
创建一个名叫 dbdata 的容器,它有一个数据卷 /dbdata ,它的启动命令是 echo … 表示启动就输出一句话就结束容器(数据卷容器不需要启动即可使用)。

docker run -d –volumes-from dbdata -ti –name db1 ubuntu bash
启动一个新容器,它挂载数据卷容器 dbdata 。也就是它将完全以和 dbdata 容器 相同的容器路径挂载相同的主机目录。也就是本容器 /dbdata 和 容器 dbdata 下的 /dbdata 都是挂载的相同的主机目录。可以通过多个 –volumes-from 参数来从多个数据卷容器挂载数据,一个数据卷容器可以同时被多个容器挂载。
同样删除容器及数据卷容器默认不会实际删除数据,必须在删除最后一个容器时通过 docker rm -v 增加 -v 参数来实际删除数据。

docker run –volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
备份数据卷容器的数据。命令实际执行的操作:容器启动后,使用了 tar 命令来将 dbdata 卷备份为容器中 /backup/backup.tar 文件,也就是主机当前目录下的名为 backup.tar 的文件。

docker run -v /dbdata –name dbdata2 ubuntu /bin/bash
创建一个空数据卷容器
docker run –volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf
/backup/backup.tar
将当前目录的backup.tar 内的数据恢复到 数据卷容器 dbdata2 。命令实际执行的操作:创建一个容器,挂载 dbdata2 容器卷中的数据卷,并使用 untar 解压备份文件到挂载的容器卷中。
docker run –volumes-from dbdata2 busybox /bin/ls /dbdata
检查恢复是否成功。

网络功能

docker run -d -P training/webapp python app.py
启动一个容器, 使用 -P 参数时,docker 将会随机的映射一个 49000-49900 的端口到容器开放的网络端口。

$ sudo docker ps -l
]CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
532192b4328c training/webapp “python app.py” About a minute ago Up About a minute 0.0.0.0:32768->5000/tcp suspicious_lovelace
可以参看映射的端口号。

$ sudo docker port 5321
5000/tcp -> 0.0.0.0:32768
可以查看指定容器的端口映射。

docker run -d -p 5000:5000 training/webapp python app.py
小写的 p 映射制定的端口。可选的格式是:ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort 。

docker run -d -p 5000:5000/udp training/webapp python app.py
增加 /udp 标记表示映射 udp 端口。

$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER all — anywhere anywhere
ACCEPT all — anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT all — anywhere anywhere
ACCEPT all — anywhere anywhere

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp — anywhere 172.17.0.24 tcp dpt:5000
可以看到端口映射是通过 iptables 实现的。

docker run -d –name db -e AAA=A1 -e bbb=b1 training/postgres
docker run -d -P –name web –link db:db training/webapp python app.py
建立一个名为 db 的容器,然后创建一个名为 web 的容器,并连接到 db 容器。注意:即使不执行连接操作默认所有容器也都在同一个虚拟局域网里面,是可以互相通信的,只不过不知道docker为容器分配的ip地址而已。可以通过 docker 服务的 –icc=false 参数禁止默认容器之间的互联,同时 –iptables=true 允许 docker 修改 iptables 时,link 操作会自动添加 iptables 规则允许相互访问开放的端口。

$docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3113cef6d6b0 training/webapp “python app.py” 2 minutes ago Up About a minute 0.0.0.0:32770->5000/tcp web
b2fbf68028c3 training/postgres “su postgres -c ‘/us 4 minutes ago Up 4 minutes 5432/tcp db
文档上面说 ps 命令在NAMES字段会显示连接关系,但是实测没有…

root@vps11:~# docker exec -ti web bash
root@5a48dc56a38e:/opt/webapp# env
HOSTNAME=5a48dc56a38e
DB_NAME=/web/db
DB_PORT_5432_TCP_ADDR=172.17.0.5
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5432_TCP=tcp://172.17.0.5:5432
LS_COLORS=
DB_ENV_bbb=b1
DB_ENV_AAA=A1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/opt/webapp
DB_PORT_5432_TCP_PORT=5432
SHLVL=1
HOME=/root
DB_PORT_5432_TCP_PROTO=tcp
LESSOPEN=| /usr/bin/lesspipe %s
DB_ENV_PG_VERSION=9.3
LESSCLOSE=/usr/bin/lesspipe %s %s
_=/usr/bin/env
root@5a48dc56a38e:/opt/webapp# cat /etc/hosts
172.17.0.6 5a48dc56a38e
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.5 db 2f115673d7cd
可以看到 docker 通过环境变量及hosts文件为web容器公开了连接信息(并没有为 db 容器公开web 的信息),DB_的前缀是连接别名。同时 link 还会为web容器公开了db容器 -e 设置的环境变量(环境变量前面增加大写的被连接容器名字,小写的环境变量也会同样小写转发过去。) inspect web 能看到 link 了 db,但是看不到 db 的环境变量,只能看到 db -e 设置的自身的环境变量。

Dockerfile

# This dockerfile uses the ubuntu image
# VERSION 2 – EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..

# 基础镜像
FROM ubuntu

# Maintainer: 维护者信息
MAINTAINER docker_user [email protected]

# Commands to update the image
RUN echo “deb http://archive.ubuntu.com/ubuntu/ raring main universe” >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo “\ndaemon off;” >> /etc/nginx/nginx.conf

# 启动容器时运行的命令,如果指定多个,只执行最后一个。如果 run 时指定了命令会覆盖当前设置。
CMD /usr/sbin/nginx

FROM
格式为 FROM 或FROM :
第一条指令必须为 FROM 指令。并且,如果在同一个Dockerfile中创建多个镜像时,可以使用多个 FROM 指令(每个镜像一次)。
MAINTAINER
格式为 MAINTAINER ,指定维护者信息。
RUN
格式为 RUN  或 RUN [“executable”, “param1”, “param2”]。
前者将在 shell 终端中运行命令,即 /bin/sh -c;后者则使用 exec 执行。指定使用其它终端可以通过第二种方式实现,例如 RUN [“/bin/bash”, “-c”, “echo hello”]。
每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用\ 来换行。
CMD
支持三种格式
• CMD [“executable”,”param1″,”param2″] 使用 exec 执行,推荐方式;
• CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
• CMD [“param1″,”param2”] 提供给 ENTRYPOINT 的默认参数;
指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。
如果用户启动容器时候指定了运行的命令,则会覆盖掉 CMD 指定的命令。
EXPOSE
格式为 EXPOSE […]。
告诉 Docker 服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过 -P,Docker 主机会自动分配一个端口转发到指定的端口。
ENV
格式为 ENV 。 指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。
例如
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

ADD
格式为 ADD
该命令将复制指定的  到容器中的 。 其中  可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)。
COPY
格式为 COPY
复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 
当使用本地目录为源目录时,推荐使用 COPY。
ENTRYPOINT
两种格式:
• ENTRYPOINT [“executable”, “param1”, “param2”]
• ENTRYPOINT command param1 param2(shell中执行)。
配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效。
VOLUME
格式为 VOLUME [“/data”]。
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。相当于 docker -v 参数,为了可移植性考虑,不允许指定主机路径,只能指定容器内的路径。
USER
格式为 USER daemon。
指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。
当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如:RUN groupadd -r postgres && useradd -r -g postgres postgres。要临时获取管理员权限可以使用 gosu,而不推荐 sudo。
WORKDIR
格式为 WORKDIR /path/to/workdir。
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为 /a/b/c。
ONBUILD
格式为 ONBUILD [INSTRUCTION]。
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
例如,Dockerfile 使用如下的内容创建了镜像 image-A。
[…]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build –dir /app/src
[…]
如果基于 image-A 创建新的镜像时,新的Dockerfile中使用 FROM image-A指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。
FROM image-A
#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build –dir /app/src
使用 ONBUILD 指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild。

来自

创建镜像

编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像。

基本的格式为 docker build [选项] 路径,该命令将读取指定路径下(包括子目录)的 Dockerfile,并将该路径下所有内容发送给 Docker 服务端,由服务端来创建镜像。因此一般建议放置 Dockerfile 的目录为空目录。也可以通过 .dockerignore 文件(每一行添加一条匹配模式)来让 Docker 忽略路径下的目录和文件。

要指定镜像的标签信息,可以通过 -t 选项,例如

$ sudo docker build -t myrepo/myapp /tmp/test1/

No comment »

golang recover() 的一个坑,记录一下。

今天碰到了一个 recover() 的一个坑。

有一个操作预期可能出现 panic 而且不需要关心它是否panic.就建立了一个匿名函数将操作包裹起来并 追加了 defer recover。 原来都是 defer func(){_=recover()}(),这次省掉了匿名函数,直接 defer recover() 。

 
package main

func main(){
    defer recover()
    panic("123")
}
/*

panic: 123

goroutine 1 [running]:
main.main()
        D:/golang/src/github.com/gamexg/go-test/鍩烘湰璇硶/recover娴嬭瘯/1/1.g         
o:5 +0x97
exit status 2
*/

结果如上,直接挂了。recover 无效…

查看了 recover 的文档:recover 只能放到 defer 函数里面,不能放到子函数。实测直接 defer recover() 也不行。recover 是内置函数,源码需要到go的实现里面查,懒得去查了。

go 的很多设计感觉都是黑魔法,就像这里,如果 panic 是一个协程本地变量保存的,那么在什么地方调用 recover 都行。但是看情况不是,恐怕和函数调用搅到一起了。

v:=dict[key]、 v,ok:=dict[key] 这个设计也是使用的黑魔法,感觉是一开始只设计了第一种实现,但是由于需要获得是否存在key,就另加了第二个实现,很生硬的感觉。

chan 实现也比较奇怪,连接之类的关闭都是 conn.Close() 实现关闭,chan 却是 close(chan),设计并不统一,很别扭。

Comments (1) »

完整的golang 多协程+信道 任务处理示例

有几个地方需要注意:for i + 协程时如果协程使用 i ,那么需要增加 i:=i 来防止多协程冲突;实际执行任务时需要用一个函数包起来,防止单个任务panic造成整个程序崩溃。

 
package main
import (
    "sync"
    "fmt"
)

/*
一个标准的协程+信道实现

*/

func main() {

    taskChan := make(chan int)
    TCount := 10
    var wg sync.WaitGroup //创建一个sync.WaitGroup

    // 产生任务
    go func() {
        for i := 0; i < 1000; i++ {
            taskChan <- i
        }
        // 全部任务都输入后关闭信道,告诉工作者进程没有新任务了。
        close(taskChan)
    }()

    // 告诉 WaitGroup 有 TCount 个执行者。
    wg.Add(TCount)
    // 启动 TCount 个协程执行任务
    for i := 0; i < TCount; i++ {

        // 注意:如果协程内使用了 i,必须有这一步,或者选择通过参数传递进协程。
        // 否则 i 会被 for 所在的协程修改,协程实际使用时值并不确定。
        i := i

        go func() {

            // 协程结束时报告当前协程执行完毕。
            defer func() { wg.Done() }()

            fmt.Printf("工作者 %v 启动...\r\n", i)

            for task := range taskChan {

                // 建立匿名函数执行任务的目的是为了捕获单个任务崩溃,防止造成整个工作者、系统崩溃。
                func() {

                    defer func() {
                        err := recover()
                        if err != nil {
                            fmt.Printf("任务失败:工作者i=%v, task=%v, err=%v\r\n", i, task, err)
                        }
                    }()

                    // 故意崩溃,看看是不是会造成整个系统崩溃。
                    if task%100==0{
                        panic("故意崩溃啦")
                    }

                    // 这里的 task 并不需要通过参数传递进来。
                    // 原因是这里是同步执行的,并不会被其它协程修改。
                    fmt.Printf("任务结果=%v ,工作者id=%v, task=%v\r\n",task*task,i,task)
                }()
            }

            fmt.Printf("工作者 %v 结束。\r\n", i)
        }()
    }

    //等待所有任务完成
    wg.Wait()
    print("全部任务结束")
}

Comments (2) »

socket.io-python-emitter 发送消息 socket.io-redis 无法收到的问题。

之前将服务迁移到 docker ,结果发现 socket.io-python-emitter 发送的消息全部发送不出去。经检查发现是 socket.io-redis 0.2.0 做了性能优化,与老版本不兼容了,无奈查源码自己打了个补丁(现已合并至主干)。

最开始是发现网站发起的推送延迟很厉害,而且推送 ping 日志显示丢包达到了80%以上。由于所有推送操作同时使用了极光推送及自建的 socket.io 推送。一开始没想到两套推送方案全部挂了,以为是测试设备出了问题,并且测试时设备日志记录了一次极光推送被强制关闭的log。同时发现极光推送控制台日志显示设备不在线(后来发现近期极光推送非常不稳定,这种延迟是常规状态了,由于原来自建推送可用, 所以没有注意)。

但是经检查发现设备没有问题,而且设备状态上报部分同样使用的 socket.io,也很正常。所以又测试了一下python向网页端推送,发现还是收不到推送,确认python发出的推送出现故障了。

无奈进 redis 数据库查看推送是否进入了数据库,结果发现python发出的推送消息的确进入了redis数据库,但是和socket.io本身发出消息的频道名称不一致。找了一下,没有当时的记录了,大体是执行的redis-cli命令,通过 PSUBSCRIBE * 订阅所有频道。

于是觉得可能是频道名字的问题,所以仿着 socket.io 消息的格式通过 PUBLISH 频道名称 消息 发了个消息,无效,并且命令返回了1,只有手工PSUBSCRIBE * 的订阅,也就是 socket.io 过呢本没有接收消息。觉得是 socket.io 有问题,岁查看代码,发现socket.io-redis 会根据房间名订阅消息,并不会订阅 #emitter 频道的消息,就很纳闷之前的系统为什么能工作。检查了下修订历史,发现是 0.1.4 -> 0.2.0 为了性能只订阅相关房间的消息,看了一下发现 python 的代码是2年前了,和当前的实现不兼容…

无奈只能自己修改了,中间又发现新版本同时还修改了包格式,不过都问题不大。

这次修bug理清了大部分 socket.io-redis 的流程。

socket.io-redis 接收消息是通过订阅 redis 上面相关的频道来实现的,会检查 uid 看看是不是本机发出的,如果不是就转到 socket.io 处理。不过这里有个问题,uid 是随机生成的6位字符串,也就是有可能碰到相同的uid,socket.io-redis 会抛弃相同uid的消息造成相同uid的 socket.io 之间无法通行。

发送消息是通过向 redis 发送消息,然后其他 socket.io-redis 订阅并接收实现的。广播消息会直接发送到 prefix + ‘#’ + packet.nsp + ‘#’ ;发送到指定房间的消息会对涉及到的房间都发送一条 prefix + ‘#’ + packet.nsp + ‘#’ + room + ‘#’ 频道的消息。

No comment »

删除 adb emulator-5554 device 的办法

adb devices 多了一个 emulator-5554 设备,所有adb 的操作都需要手动指定设备名称,实在太麻烦了。

多次检查确定所有模拟器都关闭了,检查 5554 端口,确认并没有程序使用这个端口,最后终于找到了原因,adb 会搜索 5555 – 5683 端口,发现有打开的端口就会认为存在模拟器。我的5555端口是打开的,所以adb增加了一个迷你其,但是名字还是5554就比较坑人了。关闭5555端口后重启adb,emulator-5554 设备就消失了。

http://stackoverflow.com/questions/13017269/adb-devices-showing-dummy-device

No comment »