Python 中的 id() 函数

在 python 中,有个 id() 函数,是用来获得一个 Object 的 id 的,在文档中介绍也非常简单:

id(object)

Return the “ identity ” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.

这是 python 为每个 Object 在运行时定制了一个唯一 id,但是需要注意的是,当两个 Object 当生命域不重叠的时候,可能会有相同的 id。

Object's Identity

对于任何一个 Object 来说,都有一个 id,如果多个变量指向了同一个静态常量,那么这几个变量讲拥有同样的 id。

>>> s1 = "abc"
>>> s2 = "abc"
>>> s1 == s2
True
>>> id(s1)
4390873208
>>> id(s2)
4390873208
>>> s1 is s2
True

如果两个不同的变量(常量),肯定将有不同的 id。

>>> id("abc")
4390873208
>>> id("abcd")
4391753216

is 比较

在 python 中,有两种常用的比较方式,一个是 == 比较,一个是 is 比较,前者是用来比较值是不是相等,后者是判断是不是属于同一个对象。其实,在 python 中,is 比较便是通过 id() 来实现的。当两个 Objec 拥有相同的 id 时,便会认为这是两个相同的对象。

因此,可以认为:object1 is object2 等价于 id(object1) == id(object2)

例外

今天在 v2ex 看到一个好玩的帖子(https://www.v2ex.com/t/336021 ),提到了 id([1]) == id([2]) 会返回 True

而我跑了一组数据,结果也是很有意思:

>>> id([1]) 
4391579520 
>>> id([2]) 
4391579520 
>>> a = [1] 
>>> b = [1] 
>>> id([1]) 
4391757944 
>>> id([2]) 
4391757944 
>>> id(a) 
4391579520 
>>> id(b) 
4391641672 
>>> id([1]) 
4391787072
>>> id([2]) 
4391787072  

因为在文档中已经提到了,两个不同生命域的对象可能会有相同的 id,因此出现这种情况也不例外,毕竟 [1][2],现在的确是两个不同的生命域的常量,因为这两个常量没有被任何变量引用,当使用结束之后,便会被回收掉,因此会出现两个不同的常量出现相同 id 的情况。

当给他们添加引用,会发现这个 id 就被确定到这个一个 [1] 常量上了,这是因为 这个 [1] 的生命域并没有结束(还被引用不能被回收),因此,新的 list object 不会再使用这个 id 而是换了新的 id。

2017/1/21 posted in  Python 黑魔法

图灵机器人

偶然间发现居然有一家公司提供图灵机器人的 API(http://www.tuling123.com ),突然觉得可以做些好玩的东西,比如接入 DaoVoice。

在之前就在博客上接入 Voice,但是基本上没怎么用过,索性就拿自己的博客做个试验。

获得 API

图灵机器人 注册下,然后创建一个机器人拿到 APIkey:

然后可以使用官方提供的 API,进行简单的对话:

# POST /openapi/api

+ Request (application/json; charset=utf-8)

        {
            "key": "<APIKey>",
            "info": "hello",
            "userid": "<user_id>"
        }

+ Response 200

    + Headers

            Transfer-Encoding: chunked

    + Body

            {"code":100000,"text":"hi"}

这里的 info 就是发送的对话,而 response 里的 text 就是回复。

需要注明的就是这里的 userid,这是一个可以为空的字段,如果填写的话,可以对不同的用户的对话加些区分。

这个 api 可以做很多有趣的事情,不仅仅是简单的对话,还可以提供日常服务,比如问问天气讲个笑话之类的,用法的话可以见官方文档:http://www.tuling123.com/help/h_cent_webapi.jhtml?nav=doc

接入 DaoVoice

对于对话类的接入肯定是需要实时性,那么 Voice 提供的 Webhook 就可以满足需求,而 webhook 的详细内容可以看 Voice 的文档:http://docs.daovoice.io/api/#webhook

比如当收到用户回复的时候,主要关注 conversation_idconversation_parts 里的第一个信息的 body ,前者因为这是调用 Voice 回复 api 时要用的信息,后者是因为 conversation_parts 包含了整个对话,而第一个便是最新的回复。

当拿到这两个信息之后,把 body 里的内容,发给图灵机器人获得回复之后再把回复通过 回复对话API 发送给用户就可以了。

Docker image

因为程序非常简单,就是两个 API 来回倒腾,就几行代码的事情,索性 build 出一个镜像:https://hub.daocloud.io/repos/147ff5fa-dbdd-4f79-a489-546d2fc63e9c

部署

需要几个环境,变量,缺一不可。

KEY VALUE
VOICE_TOKEN DaoVoice 的 access_token
VOICE_API https://api.daovoice.io
VOICE_ADMIN_ID DaoVoice 用来回复的用户 admin_id
REBOT_API http://openapi.tuling123.com/openapi
REBOT_TOKEN 图灵机器人的 APIKey
docker run -d -p 80:4000 -e <上面的环境变量> daocloud.io/ihypo/robot

还有一个选填的环境变量:

KEY VALUE
SAY_HELLO 当用户创建回话的默认回复

使用

这个镜像开放两个 API

ping api,用来检查看有没有还活着:

# GET /_ping

hook api,直接把这个 api 添加到 DaoVoice 的 webhook 里就可以了:

# POST /hook

注意

只保证 tag:latest 能正常工作。

试玩

不用说太多,现在就点下右下角试玩下吧。

2017/1/19 posted in  坏笔记不如好记性

容器的健康状态检查

docker 1.12新加了不少命令,而健康检查相关的命令,让容器的健康检查变得十分的简单。

可以通过 docker run --help 找到和健康检查相关的命令以及介绍:

--health-cmd string           Command to run to check health
--health-interval duration    Time between running the check
--health-retries int          Consecutive failures needed to report unhealthy
--health-timeout duration     Maximum time to allow one check to run

因此,在docker run的时候,可以通过添加 health-cmd 来明确健康检查的命令;可以通过 health-interval 命令来确定两次检查的间隔时间;health-retries 可以设定一个上限,当检查失败次数超过上限之后便会报告费健康状态;health-timeout 可以设定一次健康检查的时间上限,如果超过这个时间,便认为是检查失败的。

使用这些命令也十分简单,比如:

Hypo-MBP:~ hypo$ docker run -d --health-cmd="curl stat /etc/passwd || exit 1" --health-interval=5s --health-retries=3  --health-timeout=5s busybox sleep 10000
05e80cb5daba9521dc6b3745f1618c2ac695e0ba565957134dd0b9016f06125b
Hypo-MBP:~ hypo$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS                            PORTS                    NAMES
05e80cb5daba        busybox             "sleep 10000"            3 seconds ago        Up 2 seconds (health: starting)                            kickass_wilson

如果容器在健康状态,在docker ps的时候就会发现被注明健康。如果不在健康状态,便会显示:

2e533723454c        busybox             "sleep 10000"            About a minute ago   Up About a minute (unhealthy)                              desperate_agnesi

不仅可以通过docker cli来设定健康检查的方式,可以把健康检查的方式写到dockerfile里。还是上文的例子,写成dockerfile如下:

HEALTHCHECK --interval=5s --timeout=ss --retries 3 CMD curl stat /etc/passwd || exit 1

通过包含 HEALTHCHECK 的dockerfile构建出来的镜像,在实例化容器的时候,就具备了健康状态检查的功能。

2016/11/19 posted in  Docker

容器的运行状态获取

获得运行状态的两种方式

获得容器的运行状态主要有两种方法,一种是通过docker cli提供的stats命令查看容器的状态。另一种就是通过docker api

docker stats

通过docker stats可以获得所有的容器的状态:

docker api

默认情况下,Docker daemon监听unix://var/run/docker.sock,并且客户端必须有root权限用来与daemon交互。

为了使用Docker REST API,可以修改docker配置,添加-H标记开启远程访问:

DOCKER_OPTS="$DOCKER_OPTS -H=0.0.0.0:4232"

此处开启远程访问只是方便测试,但会因为没有添加权限验证留下安全隐患

然后就可以用过docker api与docker daemon交互:

root@ubuntu:~# curl localhost:4232/version | python -mjson.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   199  100   199    0     0  19638      0 --:--:-- --:--:-- --:--:-- 19900
{
    "ApiVersion": "1.23",
    "Arch": "amd64",
    "BuildTime": "2016-06-01T21:47:50.269346868+00:00",
    "GitCommit": "b9f10c9",
    "GoVersion": "go1.5.4",
    "KernelVersion": "3.16.0-30-generic",
    "Os": "linux",
    "Version": "1.11.2"
}

而通过remote api获得容器状态比较简单,只需要通过 /containers/<id or name>/stats 便可以获得容器状态。而且这个api会每秒一次的返回最新状态。

当然,如果只是想获得一次状态也是很简单的,这个api支持一个stream参数,支持 True/true/1False/false/0 两种只,前者会让这个api每秒返回一次状态,后者只会让这个api返回一次。默认是前者。

root@ubuntu:~# curl localhost:4232/containers/d81984a3c60d/stats?stream=0 | python -mjson.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1592  100  1592    0     0   1018      0  0:00:01  0:00:01 --:--:--  1018
{
    "blkio_stats": {
        "io_merged_recursive": [],
        "io_queue_recursive": [],
        "io_service_bytes_recursive": [],
        "io_service_time_recursive": [],
        "io_serviced_recursive": [],
        "io_time_recursive": [],
        "io_wait_time_recursive": [],
        "sectors_recursive": []
    },
    "cpu_stats": {
        "cpu_usage": {
            "percpu_usage": [
                348564780
            ],
            "total_usage": 348564780,
            "usage_in_kernelmode": 10000000,
            "usage_in_usermode": 0
        },
        "system_cpu_usage": 3000540000000,
        "throttling_data": {
            "periods": 0,
            "throttled_periods": 0,
            "throttled_time": 0
        }
    },
    "memory_stats": {
        "failcnt": 0,
        "limit": 1041981440,
        "max_usage": 6668288,
        "stats": {
            "active_anon": 6610944,
            "active_file": 4096,
            "cache": 32768,
            "hierarchical_memory_limit": 18446744073709551615,
            "inactive_anon": 12288,
            "inactive_file": 0,
            "mapped_file": 0,
            "pgfault": 1133,
            "pgmajfault": 0,
            "pgpgin": 669,
            "pgpgout": 584,
            "rss": 6594560,
            "rss_huge": 6291456,
            "total_active_anon": 6610944,
            "total_active_file": 4096,
            "total_cache": 32768,
            "total_inactive_anon": 12288,
            "total_inactive_file": 0,
            "total_mapped_file": 0,
            "total_pgfault": 1133,
            "total_pgmajfault": 0,
            "total_pgpgin": 669,
            "total_pgpgout": 584,
            "total_rss": 6594560,
            "total_rss_huge": 6291456,
            "total_unevictable": 0,
            "total_writeback": 0,
            "unevictable": 0,
            "writeback": 0
        },
        "usage": 6627328
    },
    "networks": {
        "eth0": {
            "rx_bytes": 2592,
            "rx_dropped": 0,
            "rx_errors": 0,
            "rx_packets": 32,
            "tx_bytes": 648,
            "tx_dropped": 0,
            "tx_errors": 0,
            "tx_packets": 8
        }
    },
    "pids_stats": {},
    "precpu_stats": {
        "cpu_usage": {
            "percpu_usage": [
                347925623
            ],
            "total_usage": 347925623,
            "usage_in_kernelmode": 10000000,
            "usage_in_usermode": 0
        },
        "system_cpu_usage": 2999540000000,
        "throttling_data": {
            "periods": 0,
            "throttled_periods": 0,
            "throttled_time": 0
        }
    },
    "read": "2016-10-08T00:05:40+08:00"
}
2016/10/26 posted in  Docker

容器端口映射到主机端口探究

容器的网络

在说端口之前,先明确下docker 容器的网络,可以用过docker network命令常看docker的网络:

# docker network ls
NETWORK ID          NAME                DRIVER
33b01b58a9a2        bridge              bridge
ffe4299ed1a9        host                host
fc93a0e85b75        none                null

可以看到,docker中包含bridge,host,none三种网络,其实除了这三种,还有container第四种网络。

其中,bridge是默认的网络,也就是在不指定网络模式的时候docker run使用的的网络模式,而none,正如其名,是不分配任何网络。host模式和container模式不是那么常用,前者是使用宿主机的网络,而后者是和指定的容器共享网络。

而这所讨论的容器端口与主机端口映射的原理,就是docker默认的bridge网络模式。

bridge网络

docker run一个容器,可以使用-p参数指定一个开放端口,甚至可以把这个端口映射的主机的某一端口上。比如,可以通过以下命令将nginx容器的80端口映射到主机的8080端口上:

# docker run -d -p 8080:80 --name nginx daocloud.io/library/nginx
81ea762a6bb99c1ee8c72d0fcc91f5151f8f65cf7c26747dd1f64d260845e21c
# docker ps
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS              PORTS                           NAMES
81ea762a6bb9        daocloud.io/library/nginx   "nginx -g 'daemon off"   13 seconds ago      Up 12 seconds       443/tcp, 0.0.0.0:8080->80/tcp   nginx

可以尝试访问宿主机的8080端口,可以看到是一个运行的ngxin。

使用docker inspect命令可以看到nginx容器网络相关的配置:

"NetworkSettings": {
    "Bridge": "",
    "SandboxID": "0af9e9bf575206c247c0c0e1915dfec533dc173ae55917662e8c08df19441fe3",
    "HairpinMode": false,
    "LinkLocalIPv6Address": "",
    "LinkLocalIPv6PrefixLen": 0,
    "Ports": {
        "443/tcp": null,
        "80/tcp": [
            {
                "HostIp": "0.0.0.0",
                "HostPort": "8080"
            }
        ]
    },
    "SandboxKey": "/var/run/docker/netns/0af9e9bf5752",
    "SecondaryIPAddresses": null,
    "SecondaryIPv6Addresses": null,
    "EndpointID": "16ab10c9a32ea277f3f78b589ceee0f8698feafdfe6061c9ca3ac9f9633d1af8",
    "Gateway": "172.17.0.1",
    "GlobalIPv6Address": "",
    "GlobalIPv6PrefixLen": 0,
    "IPAddress": "172.17.0.2",
    "IPPrefixLen": 16,
    "IPv6Gateway": "",
    "MacAddress": "02:42:ac:11:00:02",
    "Networks": {
        "bridge": {
            "IPAMConfig": null,
            "Links": null,
            "Aliases": null,
            "NetworkID": "33b01b58a9a2ac92011051f1f75aa94ec5eb29bdbac06580aef462d700e527ef",
            "EndpointID": "16ab10c9a32ea277f3f78b589ceee0f8698feafdfe6061c9ca3ac9f9633d1af8",
            "Gateway": "172.17.0.1",
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "MacAddress": "02:42:ac:11:00:02"
        }
    }
}

docker也提供了直接对网络进行管理的docker network命令,可以通过docker network inspect命令查看docker网络的详细信息:

# docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "33b01b58a9a2ac92011051f1f75aa94ec5eb29bdbac06580aef462d700e527ef",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "81ea762a6bb99c1ee8c72d0fcc91f5151f8f65cf7c26747dd1f64d260845e21c": {
                "Name": "nginx",
                "EndpointID": "16ab10c9a32ea277f3f78b589ceee0f8698feafdfe6061c9ca3ac9f9633d1af8",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

端口映射

通过上面可以发现,bridge只不过是ip域为172.17.0.0/16子网,通过ifconfig命令也可以获得一些信息:

# ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:72:b4:25:d9
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:72ff:feb4:25d9/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:37 errors:0 dropped:0 overruns:0 frame:0
          TX packets:39 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:3684 (3.6 KB)  TX bytes:3399 (3.3 KB)

docker是通过iptables将流量打到docker0的子网上的,因此,可以尝试找下iptables的配置:

# iptables-save |grep 172.17.0.*
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT

在上面已经得知nginx容器的ip正是172.17.0.2,iptables会将来自8080端口的流量打在80端口上。
其实在宿主机直接访问子网172.17.0.2的80端口就可以获得nginx正确的返回:

# curl 172.17.0.2:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
2016/9/19 posted in  Docker