删除子网的时候DHCP的变化

我们通常知道的答案是当删除子网的时候会会回调dhcp-agent的subnet_delete_end函数。然后调用driver的restart来重新加载dnsmasq进程,但今天我想说一点细节的东西。

##细节一:什么时候把一个网络分发到一个agent上去,

当第一个DHCP port创建后,会做2个件事情

  • 为这个network schedule到一个agent上去
  • 给所有个agent发送消息比如port_create_end

所以一个有意思的流程是,当一个subnet创建后,并不一定会通知dhcp_agent,因为要考虑这时候dhcp-port有没有创建,如果没有创建的话,说明连dhcp-agent也没有呢,自然不需要notify
那么什么时候创建dhcp-port呢?当一个subnet通过router-interface-add添加一个port的时候,就会触发dhcp-port的创建。然后schedule agent,
然后后续的subnet都会通知这些dhcp agents了

<% codeblock %>
def add_router_interface(self, context, router_id, interface_info):
add_by_port, add_by_sub = self._validate_interface_info(interface_info)
device_owner = self._get_device_owner(context, router_id)

if add_by_port:
    port = self._add_interface_by_port(
        context, router_id, interface_info['port_id'], device_owner)
elif add_by_sub:
    port = self._add_interface_by_subnet(
        context, router_id, interface_info['subnet_id'], device_owner)
    super(L3_NAT_db_mixin, self).send_dhcp_notification(
        context, {'port': port}, 'port.create.end') 

<% endcodeblock %>

所以会发送一个创建port的指令,然后会schedule一个agent来负责这个network,然后agent会做如下操作:

  • 拉subnet数据到agent
  • 创建dhcp_port(实际上是从neutron server获得分配给本dhcp port的信息,比如ip mac啥的,实际还是在本地创建)

之后再创建subnet就会notify 这个agent了。

##细节二:DHCP Port上的IP什么时候删除的

当port/subnet有更新的时候,会走restart流程,然后

  • 拉新的network数据
  • 重建dhcp_port,一般调用update_dhcp_port,然后重新设置ip网关之类的
    <% codeblock %>
    def setup(self, network, reuse_existing=False):

    """Create and initialize a device for network's DHCP on this host."""
    port = self.setup_dhcp_port(network)
    interface_name = self.get_interface_name(network, port)
    
    if ip_lib.device_exists(interface_name,
                            self.root_helper,
                            network.namespace):
        if not reuse_existing:
            raise exceptions.PreexistingDeviceFailure(
                dev_name=interface_name)
    
        LOG.debug(_('Reusing existing device: %s.'), interface_name)
    else:
        self.driver.plug(network.id,
                         port.id,
                         interface_name,
                         port.mac_address,
                         namespace=network.namespace)
    
    ip_cidrs = []
    for fixed_ip in port.fixed_ips:
        subnet = fixed_ip.subnet
        net = netaddr.IPNetwork(subnet.cidr)
        ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen)
        ip_cidrs.append(ip_cidr)
    
    if (self.conf.enable_isolated_metadata and
        self.conf.use_namespaces):
        ip_cidrs.append(METADATA_DEFAULT_CIDR)
    
    self.driver.init_l3(interface_name, ip_cidrs,
                        namespace=network.namespace)
    
    # ensure that the dhcp interface is first in the list
    if network.namespace is None:
        device = ip_lib.IPDevice(interface_name,
                                 self.root_helper)
        device.route.pullup_route(interface_name)
    
    if self.conf.use_namespaces:
        self._set_default_route(network, port=port)
    return interface_name
    

<% endcodeblock %>

##细节二:在Neutron Server上,那个子网的IP什么时候删除的

Neutron中的流表

br-int, br-tun

br-int是一个ovs桥,不是linux bridge,这个ovs桥把本计算节点上的所有vm的port连接上,那怎么才能让这些不同租户的port不互相干扰呢,就是用vlan,每个租户分配不同的vlan tag。

br-tun是用于东西流量的,比如一个租户的计算节点1上的虚拟机,为了和计算节点2上的虚拟机通讯,两者之间就会建一个tunnel。
那么一个计算借点会有多少个tunnel呢?就是看这个节点上的虚机和多少个计算节点上的续集有联系,最差的情况就是:如果有N计算几点,就有N-1个tunnel(网络节点另算)

neutron里的flow table

为了使逻辑更加清晰,在neutron中,flow table使用了多个table来分别控制不同的flow

[neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py]
1
2
3
4
5
6
7
8
9
PATCH_LV_TO_TUN = 1
GRE_TUN_TO_LV = 2
VXLAN_TUN_TO_LV = 3
LEARN_FROM_TUN = 10
UCAST_TO_TUN = 20
FLOOD_TO_TUN = 21
ARP_RESPONDER = 22

CANARY_TABLE = 128

当一个port创建的时候,会在context里设置init_flag = True.

1
2
3
if context.init_flag:
need_notify_agent_port_at = True
need_pop_flooding_entry = True

设置了need_pop_flooding_entry的用途是啥呢?

1
2
3
4
5
# Notify other agents to add fdb rule for current port
if need_pop_flooding_entry:
# And notify other agents to add flooding entry
other_fdb_entries[network_id]['ports'][agent_ip].append(
const.FLOODING_ENTRY)

这样就会向该网络的所有port所在的ovs-agent广播一下,广播的结果是,在这些ovs-agent所在的机器上,加入一条flow table,
当这个netwwrk有广播包的时候,给我一份,比如响应arp啥的。

创建虚机Port流程

1.port的创建

这我暂时不清楚,当一个虚机创建后会在br-int上添加一个端口,从neutron的角度来看,一个port已经建好了。

2.port的更新

当一个port接入到br-int上后,要做的事情就是如何让这个port和本用户的其他port,以及一些network service port(DHCP,DNS,Gateway)能通信。

具体流程是咋样的呢?

2.1.发现port

在ovs-agent里,会周期的调用rpc_loop来检查port的添加,删除,然后及时更新各个节点的流表。
在ovs-agent的内存里记录了一份本节点的所有port列表,然后周期的检查当前是否发生变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def process_network_ports(self, port_info, ovs_restarted):
#.......
#哪些端口是有变化的,哪些是新加的
devices_added_updated = (port_info.get('added', set()) |
port_info.get('updated', set()))
need_binding_devices = []
security_disabled_ports = []
if devices_added_updated:
start = time.time()
(skipped_devices, need_binding_devices,
security_disabled_ports, failed_devices['added']) = (
# 处理函数,会汇报给neutron server
self.treat_devices_added_or_updated(
devices_added_updated, ovs_restarted))

#.....
# TODO(salv-orlando): Optimize avoiding applying filters
# unnecessarily, (eg: when there are no IP address changes)
added_ports = port_info.get('added', set())
self._add_port_tag_info(need_binding_devices)

然后我们看treat_devices_added_or_updated这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def treat_devices_added_or_updated(self, devices, ovs_restarted):
#.....
if 'port_id' in details:
LOG.info(_LI("Port %(device)s updated. Details: %(details)s"),
{'device': device, 'details': details})
details['vif_port'] = port
need_binding = self.treat_vif_port(port, details['port_id'],
details['network_id'],
details['network_type'],
details['physical_network'],
details['segmentation_id'],
details['admin_state_up'],
details['fixed_ips'],
details['device_owner'],
ovs_restarted)
#...........
self._update_port_network(details['port_id'],
details['network_id'])

这里边主要2个函数,treat_vip_port,里面主要是调用port_bound。主要是为本地port设置一些属性
因为ovs-agent只是扫描到了这个port而已,需要从neutron-server获得到这个port的详细信息,然后在本地设置这个port。
设置完毕后调用_update_port_network来向neutron-server更新换个port,从BUILD更新到ACTIVE状态。

注意后面一节代码,新添加的port,我们需要分配vlan tag,在br-int上隔离不同租户的虚拟机,这个vlan tag只在内存里,没有入库,所以重启ovs-agent后对待这个问题会有点麻烦。

后面_update_port_network在J版本到时候会调用update_device_up来更新状态。但我看在M版本里代码已经变了,是在一次loop里一起更新up和down。

1
2
3
4
if devices_up or devices_down:
devices_set = self.plugin_rpc.update_device_list(
self.context, devices_up, devices_down, self.agent_id,
self.conf.host)

2.2. update_port在ml2 plugin里的处理。

update_port_statu会层层调用到update_port_postcommit,在l2pop/mech_driver.py里
这里面干什么呢,就是和流表相关的处理了

[l2pop/mech_driver.py]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def update_port_postcommit(self, context):
port = context.current
orig = context.original

diff_ips = self._get_diff_ips(orig, port)
if diff_ips:
self._fixed_ips_changed(context, orig, port, diff_ips)
if port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE:
# DVR的处理
elif (context.host != context.original_host
and context.original_status == const.PORT_STATUS_ACTIVE
and context.status == const.PORT_STATUS_DOWN):
# The port has been migrated. Send notification about port
# removal from old host.
fdb_entries = self._get_agent_fdb(
context.original_bottom_bound_segment,
orig, context.original_host)
self.L2populationAgentNotify.remove_fdb_entries(
self.rpc_ctx, fdb_entries)
elif context.status != context.original_status:
if context.status == const.PORT_STATUS_ACTIVE:
self._update_port_up(context)
elif context.status == const.PORT_STATUS_DOWN:
fdb_entries = self._get_agent_fdb(
context.bottom_bound_segment, port, context.host)
self.L2populationAgentNotify.remove_fdb_entries(
self.rpc_ctx, fdb_entries)

第一个elif是说port发生迁移,原来是ACTIVE,当前是DOWN,删除流表
第二个elif 处理状态发生变化了怎么处理,如果当前是ACTIVE的,则调用_update_port_up,否则就删除这些fdb_enties
我们先看看_update_port_up吧,猜也能猜出来,这是添加flow entry的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def _update_port_up(self, context):
port = context.current
agent_host = context.host
session = db_api.get_session()
agent = l2pop_db.get_agent_by_host(session, agent_host)
if not agent:
LOG.warning(_LW("Unable to retrieve active L2 agent on host %s"),
agent_host)
return

network_id = port['network_id']
#当前ovs-agent上port的个数,用于判断自己是不是第一个
agent_active_ports = l2pop_db.get_agent_network_active_port_count(
session, agent_host, network_id)
agent_ip = l2pop_db.get_agent_ip(agent)
segment = context.bottom_bound_segment
if not self._validate_segment(segment, port['id'], agent):
return
# 自己新加一个port,对别的agent上的本network的影响(老port新加流表到新port)
other_fdb_entries = self._get_fdb_entries_template(
segment, agent_ip, network_id)
other_fdb_ports = other_fdb_entries[network_id]['ports']

if agent_active_ports == 1 or (l2pop_db.get_agent_uptime(agent) <
cfg.CONF.l2pop.agent_boot_time):
# First port activated on current agent in this network,
# we have to provide it with the whole list of fdb entries
agent_fdb_entries = self._create_agent_fdb(session,
agent,
segment,
network_id)

# And notify other agents to add flooding entry
other_fdb_ports[agent_ip].append(const.FLOODING_ENTRY)
# 给新创建的port添加到已有port的流表
if agent_fdb_entries[network_id]['ports'].keys():
self.L2populationAgentNotify.add_fdb_entries(
self.rpc_ctx, agent_fdb_entries, agent_host)

# Notify other agents to add fdb rule for current port
if port['device_owner'] != const.DEVICE_OWNER_DVR_INTERFACE:
other_fdb_ports[agent_ip] += self._get_port_fdb_entries(port)
# 向老port广播到新port的流表这么走
self.L2populationAgentNotify.add_fdb_entries(self.rpc_ctx,
other_fdb_entries)

前面的代码大致就分2部分,第一个部分是如果自己是本agent的第一个port,则同步所有流表过来
否则,就全部广播一下。

Neutron之安全组极代码分析

摘要

在传统的网络中,我们通常要限制客户端对主机的访问,基本上是为了安全的考虑,比如禁止外网用户对3389端口的访问。在linux中可以通过免费的iptables来实现。自然,在云环境中,用户自然也有这样的需求。

安全组是什么

可以在官方看他的定义
我理解的安全组就是可以绑定在port上的一组规则,这些规则可以限制数据包的入栈和出栈
一个port可以关联好多安全组

安全组和FWaaS的区别

iptables

默认情况下,安全组是通过iptables来实现的,iptable的规则配置在哪里呢,先看这个图:
neutron 网络组件
每个port有个qbr,iptables的规则就配置在这个qbr上,我们可以看个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
[root@206-215-45-34-55 neutron]# iptables -nL
Chain INPUT (policy ACCEPT)
target prot opt source destination
neutron-openvswi-INPUT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:53
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:67
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:67

Chain FORWARD (policy ACCEPT)
target prot opt source destination
neutron-filter-top all -- 0.0.0.0/0 0.0.0.0/0
neutron-openvswi-FORWARD all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 192.168.122.0/24 state RELATED,ESTABLISHED
ACCEPT all -- 192.168.122.0/24 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-port-unreachable
REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-port-unreachable

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
neutron-filter-top all -- 0.0.0.0/0 0.0.0.0/0
neutron-openvswi-OUTPUT all -- 0.0.0.0/0 0.0.0.0/0

Chain neutron-filter-top (2 references)
target prot opt source destination
neutron-openvswi-local all -- 0.0.0.0/0 0.0.0.0/0

Chain neutron-openvswi-FORWARD (1 references)
target prot opt source destination
neutron-openvswi-sg-chain all -- 0.0.0.0/0 0.0.0.0/0 PHYSDEV match --physdev-out tap259c021c-42 --physdev-is-bridged
neutron-openvswi-sg-chain all -- 0.0.0.0/0 0.0.0.0/0 PHYSDEV match --physdev-in tap259c021c-42 --physdev-is-bridged

Chain neutron-openvswi-INPUT (1 references)
target prot opt source
neutron-openvswi-o259c021c-4 all -- 0.0.0.0/0 0.0.0.0/0 PHYSDEV match --physdev-in tap259c021c-42 --physdev-is-bridged

Chain neutron-openvswi-OUTPUT (1 references)
target prot opt source destination

Chain neutron-openvswi-i259c021c-4 (1 references)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0 state INVALID
RETURN all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
RETURN udp -- 192.168.200.2 0.0.0.0/0 udp spt:67 dpt:68
neutron-openvswi-i273bfab5-6 all -- 0.0.0.0/0 0.0.0.0/0
neutron-openvswi-sg-fallback all -- 0.0.0.0/0 0.0.0.0/0

Chain neutron-openvswi-i273bfab5-6 (1 references)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0 state INVALID
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
ACCEPT all -- 192.168.200.3 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0

Chain neutron-openvswi-local (1 references)
target prot opt source destination

Chain neutron-openvswi-o259c021c-4 (2 references)
target prot opt source destination
RETURN udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:68 dpt:67
neutron-openvswi-s259c021c-4 all -- 0.0.0.0/0 0.0.0.0/0
DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:67 dpt:68
DROP all -- 0.0.0.0/0 0.0.0.0/0 state INVALID
RETURN all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
neutron-openvswi-o273bfab5-6 all -- 0.0.0.0/0 0.0.0.0/0
neutron-openvswi-sg-fallback all -- 0.0.0.0/0 0.0.0.0/0

Chain neutron-openvswi-o273bfab5-6 (1 references)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0 state INVALID
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0

Chain neutron-openvswi-s259c021c-4 (1 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0

Chain neutron-openvswi-sg-chain (2 references)
target prot opt source destination
neutron-openvswi-i259c021c-4 all -- 0.0.0.0/0 0.0.0.0/0 PHYSDEV match --physdev-out tap259c021c-42 --physdev-is-bridged
neutron-openvswi-o259c021c-4 all -- 0.0.0.0/0 0.0.0.0/0 PHYSDEV match --physdev-in tap259c021c-42 --physdev-is-bridged
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0

Chain neutron-openvswi-sg-fallback (2 references)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0

千万别以为太多了,实际上,这只是一个计算借点上的一个port而已,虚机越多,规则越多。在看明白上面的规则之前,需要理一理iptable的大致规则。

  • iptables的阶段
    iptables处理包有5个阶段,在不同阶段调用不同的回调
    1.PREROUTING (路由前)
    2.INPUT (数据包流入口)
    3.FORWARD (转发管卡)
    4.OUTPUT(数据包出口)
    5.POSTROUTING(路由后)
  • iptables的chain
    在每个阶段的回调,可以执行一个或多个规则,这些依次运行的规则可以组成一个chain,规则包括执行条件和对应的action,action可以为执行下一个chain。每个阶段都有一个默认的chain.比如我们看这个
    1
    2
    3
    4
    5
    6
    7
        Chain INPUT (policy ACCEPT)
    target prot opt source destination
    neutron-openvswi-INPUT all -- 0.0.0.0/0 0.0.0.0/0
    ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:53
    ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
    ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:67
    ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:67
    INPUT的chain,默认是全部ACCEPT,第一个规则是0.0.0.0/0 他的action不是DROP/ACCEPT,而是一个chain,意思就是所有包先过这个chain检查再说,如果这个chain检查的结果:
    1.ACCEPT:接受这个包,后面的规则可以不过了
    2.DROP:丢掉这个包,后面的规则可以不过了
    3.RETURN:继续过后面的规则,知道遇到ACCEPT或DROP为止
  • iptables的table
    table 是-t的参数,可以在3个中选择,nat, filter, mangle。
    nat:用于实现snat或者dnat
    filter:这个table里的配置用于决定包过还是不过,安全组肯定是使用这个功能
    mangle:用户修改报文内容的。

比如我们想配置一个nat出去,一台机器做nat网管可以这样配置:

1
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -j SNAT --to 10.4.5.6

192.168.0.0/24来的ip,都会翻译成10.4.5.6出去
-t nat是说这个规则是配置nat的
-A POSTROUTING,是说在POSTROUTING这个chain的最后加一条nat规则。POSTROUTING是说找完路由丢出去之前修改source IP
-j just到一个target,但好难理解,其实就是执行一个动作,比如-j DROP, -j RETURN,这里是做SNAT

所以回到上面的例子:
INPUT方向来包后,先过neutron-openvswi-INPUT 这个chain的检查,而这个chain,就是全过,当然只有网卡为tap259c021c-42才检查

1
2
3
Chain neutron-openvswi-INPUT (1 references)
target prot opt source
neutron-openvswi-o259c021c-4 all -- 0.0.0.0/0 0.0.0.0/0 PHYSDEV match --physdev-in tap259c021c-42 --physdev-is-bridged

创建安全组的过程

abonege的普吉岛游记

1.住宿

自己从携程定了4月19到4月25的普吉岛行程,入住甜蜜玛利亚艺术酒店(sugar marina ART resort)。我也搞不清resort和hotel的区别。离卡伦海滩只有走路5分钟的距离,出了胡同右转就到。

酒店不错,定的池景房,现在看很明智,出阳台就是泳池,可以从房间直接下泳池,游完直接回房间,太方便了!老婆很满意,宝宝想游随时下去,缺什么回房间就拿,水杯也放阳台上。但是提醒带孩子的,要看好孩子,熊孩子要小心别掉水里,预订房间时也有这个提示,阳台有栏杆,但是大孩子可以翻过去的。泳池边是大大高高的山竹树,结了大果子。

普吉岛有好几个海滩,芭东海滩patong beach和其他。什么卡伦/卡塔之类的。网上有好多攻略,说这里的海滩好,那里的海滩好,我觉得这只是应该考虑的很小的一部分因素。为什么呢,后面会提到。



2.交通

其实选择不大,一个是传说中的双条车,一种就是tutu车/taxi。双条车如果只有几个成人可以试试看,我们由于带了小朋友,实在不方便,而且发车时间基本30分钟一趟,晚上6点后就没有了。在暴晒之下必然熬不到等一个车,剩下就是tuktuk车和taxi,有攻略说价格差很多,但我觉得是一样的,比如从sugar marina酒店到江西冷350TBL,选什么车都是一样的价格,而且taxi是有空调的,舒服的很,但注意的是千万别带榴莲进车子甚至酒店,否则他们的脸青青的态度会很不好。。。还有一次是打车到查龙寺,那种10几个人座的面包车也是400TBL。还有一次是10多个人打面包车(van)到江西冷也是500块。所以这些车的价格都一样,人多就tuktuk(8个以上)人少就taxi。这些车都可以适当讲价,比如500可以讲价到400,400可以到350,没见过更便宜的,他们的定价是有个『标准』牌子的,立在大街上,基本不会有欺诈。

所以总体来说交通费很贵,所以为了节约交通费就要住在你打算常去的地方。比如江西冷Jungceylon / Big c / 班赞Banzaan海鲜市场附近。(这3个其实是一个地方)IMG_7118

吃,可能是大家最感兴趣的地方了,顿顿吃海鲜我也吃不起,泰国菜,吃几顿就够够的了,冬荫功汤,咖喱蟹,salad seafood,菠萝饭,芒果饭,最典型的泰国菜。说到饭,泰国的米饭是真心硬邦邦啊,一点都不好吃,难道没有用日本电饭锅来做么?

个人觉得饭馆的价格相差不会太大,比如芒果饭有的120,有的140,最贵150,一盘咖喱蟹,250到300,整个的还是去海鲜市场吧。

我印象比较深的有karon beach的red union,他家的salad seafood要好吃一些,但冬荫功汤太辣。karon sea food的冬荫功汤要好喝一些,但咖喱蟹烂极了。咖喱蟹最好吃的一家是padong的you and me(你和我)本来我是不吃螃蟹的,但吃这里的咖喱蟹后觉得真心好,后来去kalon sea food又点了一次,失望极了。

关于班赞海鲜市场,很多说这个那个的,反正就是能讲价,其实最该关注的是加工费真心贵,论斤算的,下次真可以带个锅去,我们买了4500的海鲜,加工费居然要2000,而且螃蟹和皮皮虾是清蒸!!如果个别用别的做法,超贵,不合适。1楼也有水果,要便宜些,起码比big C便宜。

周末夜市Phuket weekend market传说中的周末夜市,我没去成,因为你妹打车费太贵了,来回1000泰铢,200块RMB,我想不出 去的理由,还好班赞市场外面6点就开始有大排档了,应该和weekend market差不多,毕竟吃的就那么几样,烤鱿鱼,墨鱼,鲅鱼(他们居然说秋刀鱼),烤鸡肉,fruit shake(40泰铢)不过拷墨鱼和鱿鱼然后沾着他们的酸辣酱太好吃了,一般都是50-60,人民币10快左右,便宜啊。IMG_8165 IMG_8164 IMG_8157

另外,如果白天错过了班赞的海鲜也可以在那里吃海鲜,而且不需要加工费,价格我觉得不贵,只问了竹节蛏,500B 1kg,讲讲价估计能到400B,但那个人态度不好,所以没吃。商贩都是抓一把就和你说价钱,你问多少钱一斤他不说。

4.景点

我去了几个景点,基本都是一日游的方式,都是管水和水果,但基本太阳椅子要收费比如150泰铢,2个椅子,虽然理论上是一天,但如果跟当地半日团,导游一会就带你走了。

珊瑚岛:没有珊瑚。。。离普吉岛最近的岛几分钟就到。最有意思的是海滩左侧有很多大礁石,石头里,有很多小动物,比如2cm见方的螃蟹,孩子们很喜欢IMG_7336很多大海参,没人要,而且居然捡到了一个巨大无比的红色海星,腿都有20cm了吧,但导游立马让我们放回去,否则会死翘翘。石头那边可以探到很多宝贝,记得拿着宝宝的挖沙子的桶去哦

珊瑚岛中间导游会带去一个浮潜的地方,水很棒,很清澈,鱼很多

蛋岛:因为宝宝的原因,去了,小鱼在身边来来去去的挺好,但要拿面包喂食才会让它们游回来,刚开始有些兴奋,但过一会就乏味了

蛋岛中间也会去一个浮潜的地方,海浪大一些,没有前一天的地方好。但是老婆下去浮潜,临走时看到了nemo小丑鱼,还是导游发现招呼老婆别上船去看看,等半天在下面珊瑚里才游出来一条小的,漂亮的小丑鱼,还是比较高兴的。浮潜时间太短了,不够尽兴,带孩子不能浮潜为主。

后来又换了一个不知道叫什么名字的岛,也是有好多小鱼,可以岸边浮潜,又收了椅子费。老婆前后浮潜了好几次,但是没有珊瑚,只有浅水区的黄蓝条纹小鱼。深水的礁石没有珊瑚,可能有个别水母,鱼都被喂食吸引走了,可观赏的不多了,不过老婆看到了几条北京太平洋海洋世界里的彩色鱼,红的,蓝色,很开心。很多孩子下水腻歪了,就岸边挖沙子,沙子很细腻很白美美的



注意的是,需要带一个浮潜设备过去,我是当地买的,砍价后250泰铢,大约50块,也不贵,不过比较普通,没有漏水就是了。

骑大象:

小孩子必定喜欢的项目,国内也骑不了。就是消费环节多,先买100泰铢的香蕉,然后给驯象师100-200泰铢,然后中途再买一次100泰铢的香蕉,在大象上,不能不买啊,老婆嚷嚷买。比较颠簸,晒的时候有提供遮阳伞,驯象师是缅甸人,跟老婆攀谈起来,我们骑的大象37岁了母象,比我俩年纪都大,同行年轻的一头是23几岁,个头小,所以载轻的一家子,老板养了50多头大象呢。不过坐完老婆有点心疼大象,心里不是很舒服,估计老板也并不人道对待这些辛苦的动物,它们一直在载客。如果不带孩子,可能就不坐了。最后大象表演,有一头小象一直随音乐摇摆起舞,美美的,老婆喜欢。最后也需要给点消费。。。

查龙寺:

还是去查龙寺吧,我是最后一天去的,从Kalon打车过去400泰铢,与泰国的寺庙比,国内的寺庙太弱了,要门票,不让照相,很多不相关的佛,管你观音如来关公财神甚至乾隆爷都摆一起就怕没游客喜欢的。。。泰国的寺庙太美了,瓦是红色,其他地方金色,可以捐个几十泰铢,然后拿香和花,去拜祭,可以抽个签,里面也可以随便坐,人也不多。有高僧的塑身像,泰国人贴了很多金箔。门口一直燃鞭炮,闹心,孩子怕。据说是唯一一家可以放鞭炮的寺庙,所以华人都跑这边来放了。有一个高塔,最上面一层供奉有舍利,下面全是各种佛像,很多很多,登顶风景很好。泰国寺庙很多猫和狗,都趴着,因为太晒了吧。这里的椰子最便宜,40泰铢,好喝





查龙寺往回走,路过一些本地人小院子,附近转转,拍照了,院子里的树很好看,有车库有车,普遍是日系车。路边花大都三角堇,各种颜色,好看。

700米有个易初莲花超市lotus,算是路上的一个惊喜,超市很大,主要是为当地人服务,不是像big C那样主要为游客,所以价格不贵。水果也比big c 便宜不老少,然后门口有类似我们的美食城的东东,很多当地上班族在这里吃中饭,各种炒饭,细面,烤肉,海鲜色拉,都是50-60一碗,便宜的很。。。服务人员不太会英语,更别说中文,牌子也不会加上中英文,服务员因为不能交流,看我过去都躲着我。我们终于找到一家纯当地的吃饭地方,很高兴,吃了好多


卡伦海滩:

住在卡伦海滩,去过几次,海浪有点,人的确不多,沙子不如海岛的细软,有人放孔明灯,没看上日落,拍照美美的

补图

5.感受

大部分商贩服务人员都是热情的,配合游客照相,但感觉泰国人没那么好,一不小心碰到或者踩到鞋啥的,感觉他们很不开心。

泰国人比较懒,一般都是下午才开始营生,但下午热的 要命,要是报半天的团的话尽量报上半天的吧,下午可以回来休息。

到处都有帮你照相的,虽然不买也可以,但真不爽呢。这个一般贵,300泰铢一个压塑好的照片

6天很快就过去了,再让我 看一下这个泳池吧,孩子很喜欢,一直嚷嚷要游泳

建议:

1.住宿要在你目的地附近最好,因为交通实在不方便。(不差钱没关系)

2.google地图是必要的

3.泰国方便面可以见识一下,味道不错。711遍地是,缺什么随时买

4.带好防晒,烈日太烈了,容易晒伤

5.酒店的东西不要弄丢,检查好仔细,估计之前吃西瓜没留意丢了一个小勺子,赔了50泰铢。。。以前去别的国家旅游,似乎都是直接退房,当然以前也没搞丢搞坏什么哈哈

6.一日游不用着急报,到时看状态,携程,淘宝多的是选择。

如何在hexo使用图片和提供附件下载

在写东西的时候,有时候会加图片或者下载的附件,hexo提供的挺麻烦的,虽然可以弄个目录加图片,但附件下载一直不好用,知道看到post_asset_folder,才算解决。

使用步骤
1.打开开关,修改_config.yml,设置post_asset_folder=true

[_config.yml]
1
post_asset_folder=true

2.新建一个文章

1
hexo new 如何在hexo使用图片

这时候会自动在md文件目录生成一个同名的文件夹,把你要贴的图片附件,全都放在这里即可。

1
2
3
4
5
6
7
8
MacBook-Pro:_posts abone$ ll -lat 如何在hexo使用图片
total 1696
drwxr-xr-x 6 abone staff 204 5 25 19:31 .
drwxr-xr-x 11 abone staff 374 5 25 19:31 ..
-rw-r--r--@ 1 abone staff 6148 5 25 19:27 .DS_Store
-rw-r--r--@ 1 abone staff 22988 5 25 19:26 图片2.jpg
-rw-r--r--@ 1 abone staff 14468 5 25 19:25 图片1.jpeg
-rw-r-----@ 1 abone staff 817742 5 5 18:35 HAPROXYCODE.pdf

3.在文章里插入图片和附件链接

1
2
3
4
5
6
![这是图片1](如何在hexo使用图片/图片1.jpeg)
![这是图片2](如何在hexo使用图片/图片2.jpg)
![这是图片1_1](图片1.jpeg)
![这是图片2_1](图片2.jpg)
[这是下载](如何在hexo使用图片/HAPROXYCODE.pdf)
[这是下载1](HAPROXYCODE.pdf)

后面就是展示的结果了
这是图片1
这是图片2
这是图片1_1
这是图片2_1
这是下载
这是下载1

大家应该能看出来似乎是有个bug了,对于图片来说前面有个”!”, 结果就是加不加目录名没有关系,都可以正常显示图片,但如果是普通link(没有”!”)就必须不能写目录名(也就是文章名),要不然就打开链接失效。
反正最稳妥的方法就是不加目录名字到路径里。

也来说说Neutron的组件及其RPC通讯

1.Neutron中的各个角色

Neutron虽然只是OpenStack中的一个角色,网络,但已经足够复杂,它内部又有很多概念。(这些概念在其他的openstack角色之间也是通用的),初学者动不动就被吓得体无完肤,挣扎了近一个月,饱经摧残,真相总算渐渐浮出水面,谢谢那些帮助我的老员工,在KPI压力很大的情况下,依然抽出时间回答我的问题,bless。

Neutron可以认为是一个中心,四周插件遍地扩展的结构,中心是Core Plugin,扩展(Extention)呢,就是单独完成独立功能的一个个组件,比如负载均衡LBaaS,防火墙FWaaS之类的。他们都是以扩展的方式嵌在Core Plugin的周围。

1.1.Neutron的基础

先回答个问题:Neutron的最最基础的功能是什么呢?
管理网络,再基础一点呢?管理二层网络。没错是二层网络,所以Neutron管理的核心3大件是:Port,Network,和Subnet。至于路由,dhcp,负载均衡,防火墙都是上层的扩展。

1.2.Neutron Server

我们要介绍的第一个大角色,Neutron Server是一个实体,是一个(组)实实在在进程,它的任务这么几个:

  • 接受API过来的指令,比如创建一个network,删除一个router等等
  • 通过RPC和各个Agent通讯
  • 调度,比如一个网络的2个DHCP Agent,应该调度在那些网络节点上
  • 对插件的管理和使用。Neutron本身可以很简单,但为了支持复杂的功能,可以通过扩展不断的添加再添加

1.3.AMQP

AMQP,全称是啥?Advanced Message Queue Protocol? 也许吧,反正就是一种协议,规定了不同角色之间传递消息的格式是什么。有很多实现了AMQP协议的开源方案,我们这里用的是QPID,QPID也是一组进程,它的职责就是收到一个消息后路由给响应的对象,大家可以看Openstack RPC 通信原理

1.4.ML2

最著名,最基础的plugin,也就是core plugin。抽象出来一层逻辑,屏蔽下层复杂的的二层网络,flat,vxlan,gre之类的,都抽象成对上面3个基础组件的管理,port,network,以及subnet。

Service 与 Plugin

我理了好久关于Neutron里各个概念的意思,也看到过云耳的一片博客如何区分 OpenStack Neutron Extension 和 Plugin
但我觉得他理解的还是不对。我的理解是这样的:
从最基础的管理角度来说,core plugin管理最基础的网络组件Port,Network和Subnet。当有更丰富的网络功能需要填充的时候,比如lbaas,fwaas,vpnaas等,这些功能不是很核心,然后怎么办呢,它们是以service的方式扩展出来的。
我更觉得Core Plugin应该改名字为Core Serivce。
一个标准的Service包含4个大件:

  • Plugin. 该逻辑在Neutron Server里的实现,包含操作数据库
  • Agent. 该逻辑在网络节点的代码,接受Neutron Server的指令
  • Driver. 在Agent端驱动数据流
  • 调度. 调度给哪个Agent处理

1.5.Extention

终于提到了扩展,扩展是对Core Plugin和Service的扩充,比如,对调度模块增加一种算法,比如network需要增加一个多AZ属性等,都需要通过Extention来实现,那问了,为啥不是直接该调度和network结构呢?
显然么,作为一个被普遍使用的产品要考虑升级的兼容性,虽然升了级,但不想用这个extention,不夹在这个extentions即可。
使用下面的命令,可以看当前的neutron开启了哪些扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
neutron@mynet-206-215-44 ~ $ neutron ext-list
+-----------------------+-----------------------------------------------+
| alias | name |
+-----------------------+-----------------------------------------------+
| ext-gw-mode | Neutron L3 Configurable external gateway mode |
| security-group | security-group |
| l3_agent_scheduler | L3 Agent Scheduler |
| fwaas | Firewall service |
| binding | Port Binding |
| provider | Provider Network |
| agent | agent |
| quotas | Quota management support |
| dhcp_agent_scheduler | DHCP Agent Scheduler |
| l2pop_fdb_entries | l2pop_fdb_entries |
| multi-provider | Multi Provider Network |
| external-net | Neutron external network |
| router | Neutron L3 Router |
| allowed-address-pairs | Allowed Address Pairs |
| extra_dhcp_opt | Neutron Extra DHCP opts |
| extraroute | Neutron Extra Route |
+-----------------------+-----------------------------------------------+

我个人认为扩展是针对功能说的,一个extention支持一个或几个功能,但从软件的角度讲,扩展包括下面几个部分:

1.5.1.Agent

Agent是相对于Neutron Server的,一般管Nentron Server所在的点叫控制节点,Agent所在的节点为网络节点,虚拟机所在的节点为计算节点。
Agent是干嘛用的呢,字面意思就能看出来,向Agent所在的机器执行neutron server发过来的指令,在有必要的情况下也向neutorn server反馈一些信息。

1.5.2.Plugin

了解了Agent后,Plugin就轻松一些了,Plugin可以认为是Agent在Neutron Server端的逻辑,因为Neutorn是插件化的东西,比如想基于neutron开发一个功能负载均衡,需要在neutron server端和agent端各自实现负载均衡的逻辑,要不然只在Agent端实现了,neutron server这边不知道具体逻辑是什么,怎么通讯呢?
此外,Plugin还有一个很重要的功能,就是读写数据库,因为一般一个功能都需要读写数据库。

1.5.3.Driver

Extention的Driver基本上是和Agent在一起的,一个进程里包含这2个部分。Agent主要负责和Neutron Server(plugin)的交互,而driver呢,主要用于实现对该extention的数据平面对应的管理平面的配置。
说的有点绕啊,举个例子就明白了:
比如你想用haproxy实现一个负载均衡的扩展,LBAgent用于从Neutron Server接受创建/销毁 负载均衡实例的请求,然后告诉driver处理,driver干什么呢?就是生成haproxy的配置文件,然后启动一个haproxy的进程来加载配置文件。
所以Driver还是只是管理平面的一个东西。

2.Neutron角色之间的的RPC通讯

2.1.RPC是什么

好,基本概念介绍完了后,我们可以开始我们的主题了,那就是RPC通讯,一般来讲,RPC通讯就是Agent和Plugin的通讯,或者说是Agent和Neutron Server的通讯,其实Plugin本身对外只提供函数,从AMQP消息翻译成对应的Plugin里的函数的操作已经在Neutron Server里完成了。所以基本上就是Agent发一个消息,Plugin里就有一个函数被调用了,我们这些主要想弄清它们是怎么关联的。

2.2.基本规则

通讯分2端,一般我们把主动发起的这段叫客户端,另一端为server端。RPC也分2个方向上的通讯,从Agent到Neutron Server和反过来的。

2.2.1.客户端

客户端都是通过继承RpcProxy来实现,这个类不能直接找到,在rpc_inner目录里的proxy.py。 派生类的命名方式都是XxxxApi的格式。
然后还可以细化成两类:

  • XxxPluginXxxApi: 这类基本上都是Agent发起的对Plugin的调用,在Agent端封装发送消息,在Plugin端实现动作。
  • XxxNotifierApi: 这类基本上是在Plugin(Neutron Server)封装发送消息,在Agent端实现动作。
    客户端继承了这些Api,只是发送出来消息而已,需要在Server端实现对应的动作,可以简单的看一点代码:
    比如DHCP Agent想为一个用户启动一个服务dnsmasq的时候,需要先创建一个port,但这个port自己创建不了,需要通知neutron server,然后通过openvswitch来做,这个在DhcpPluginApi是这么干的:
    [python]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class DhcpPluginApi(proxy.RpcProxy):
    def create_dhcp_port(self, port):
    """Make a remote process call to create the dhcp port."""
    port = self.call(self.context,
    self.make_msg('create_dhcp_port',
    port=port,
    host=self.host),
    topic=self.topic)
    if port:
    return dhcp.DictModel(port)
    所以它只是从客户端的角度发出来一个msg而已。

    2.2.2.服务端

    上面提到过客户端是怎么干的,那么服务端怎么找到对应的逻辑呢,我们虽然不需要知道,怎么从msg翻译成函数的,但需要知道在Neuron端如何实现相应的逻辑:
  • Neutron Server作为服务端
    在这里也有类似的命名规则:
    通常实现逻辑的类都叫XxxRpcCallback,或者后面有个Mixin,XxxRpcCallbackMixin,比如上面的dhcp的例子,在Neutron Server里,会有一个类DhcpRpcCallbackMixin里实现一个create_dhcp_port的功能。

那么这次callback是怎么注册的呢?让Neutron Server知道这个消息来了,该谁处理。
通常callback是plugin的一部分,plugin初始化的时候,会创建consumer,并说我关注哪些topic。比如Ml2Plugin是这么干的:

[python]
1
2
3
4
5
6
7
8
def start_rpc_listener(self):
self.callbacks = rpc.RpcCallbacks(self.notifier, self.type_manager)
self.topic = topics.PLUGIN
self.conn = c_rpc.create_connection(new=True)
self.dispatcher = self.callbacks.create_rpc_dispatcher()
self.conn.create_consumer(self.topic, self.dispatcher,
fanout=False)
return self.conn.consume_in_thread()

上面是说这个callback关心PLUGIN topic

而openvswitch的plugin是这么干的:

[python]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def setup_rpc(self):
# RPC support
self.service_topics = {svc_constants.CORE: topics.PLUGIN,
svc_constants.L3_ROUTER_NAT: topics.L3PLUGIN}
self.conn = rpc.create_connection(new=True)
self.notifier = AgentNotifierApi(topics.AGENT)
self.agent_notifiers[q_const.AGENT_TYPE_DHCP] = (
dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
self.agent_notifiers[q_const.AGENT_TYPE_L3] = (
l3_rpc_agent_api.L3AgentNotify
)
self.callbacks = OVSRpcCallbacks(self.notifier, self.tunnel_type)
self.dispatcher = self.callbacks.create_rpc_dispatcher()
for svc_topic in self.service_topics.values():
self.conn.create_consumer(svc_topic, self.dispatcher, fanout=False)
# Consume from all consumers in a thread
self.conn.consume_in_thread()

上面是说这个callback关心那2个topic,即PLUGIN和L3PLUGIN
所以来一个msg后,先找配置的TOPIC的consumer,然后根据消息的名字,找处理函数。

  • Agent作为服务端
    此外还有另一个方向的,即Neutron Server要向Agent广播消息,比如一个port创建了,需要告诉所有的OVSAgent,干嘛干嘛。
    这时候Neutron Server这端,是通过继承NotifierAPI来通知的,然后这时候Server端就是Agent端了。
    那么Agent端怎么做才能收到,处理这些消息呢?2种方式:
    继承RpcCallback方式
    这个方式和Neutron Server端的实现一样,继承某某RpcCallback函数,然后在初始化的时候创建consumer。OVSNeutronAgent就这么干的
    [python]
    1
    2
    class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
    l2population_rpc.L2populationRpcCallBackMixin):
    Service的方式
    不需要继承RpcCallback函数,但需要实现一样的逻辑函数,然后通过neutron_service.Service.create来创建一个Service,这个Service会根据收到的消息,调用你的实现函数,DHCPAgent就这么干的。
    [python]
    1
    2
    3
    4
    5
    server = neutron_service.Service.create(
    binary='neutron-dhcp-agent',
    topic=topics.DHCP_AGENT,
    report_interval=cfg.CONF.AGENT.report_interval,
    manager='neutron.agent.dhcp_agent.DhcpAgentWithStateReport')
    DhcpAgentWithStateReport是DHCPAgent的实现类,里面有callback函数,比如network_create_end, networkd_delete_end等。

在neutron数据库增加一个列

起因是对agent有这样的需求:
-对特定的dhcp-agent,有特定标识后,只能手动调度过去,不能自动调度过去
所以考虑增加一列用于这样的标识

[sql]
1
alter table agents add column auto_schedule tinyint(1) default 0 not null;

添加完了之后,执行neutron agent-update并不生效,会提示属性不存在,这里还需要修改2个文件

[neutron/extensions/agent.py]
1
2
3
4
5
6
53         'description': {'allow_post': False, 'allow_put': True,
54 'is_visible': True,
55 'validate': {'type:string': None}},
56 'auto_schedule': {'allow_post': False, 'allow_put': True,
57 'convert_to': attr.convert_to_boolean,
58 'is_visible': True},
[neutron/db/agents_db.py]
1
2
3
67     # configurations: a json dict string, I think 4095 is enough
68 configurations = sa.Column(sa.String(4095), nullable=False)
69 auto_schedule = sa.Column(sa.Boolean, nullable=False, default=False)

然后就可以通过neutron agent-updage来更新这个列了

[CLI]
1
2
3
[test@mypc services]$ neutron agent-update --request-format json f7952a27-cf66-4b07-beb8-536f5d44fc42 --auto_schedule=True
Updated agent: f7952a27-cf66-4b07-beb8-536f5d44fc42

Tengine比Nginx好在哪里??

最近在做nginx和haproxy的对比,一不小心看到了阿里的开源tengine,感觉有点 意思,也许在后续的产品中会选用它,主要是有这么几个考虑:

1.tengine会比nginx稳定,当然这只是感觉,但毕竟tengine是在taobao,tmall上都有使用的,但至于是不是大规模使用,没人知道,知道也不说。
2.比原生nginx更好的支持health check,这对LB来说是个基本的,虽然nginx有第三方的module来支持,但信不过
3.支持upstream的动态增减,当我看到这个feature之后,眼睛呼啦一亮,但很快就失望了。他说的动态的前提是要知道哪些backend server可用,先把IP之类的列出来,可以通过restful API加减,但这个前提太蛋疼了,我能不能动态的增加现在不可知的backend server呢?
4.监控还是老样子,太简单了。
5.动态加载模块对我来说没有用。
6.有一些加速功能,比如把几个http request meger成一个请求,目的是减少请求数,以及过滤文件中的空白什么的 用来减少体积。
7.过载保护,过载的时候提示配置页面,但我不知道是只对新建连接才这样还是已经建立会话的还这样,如果是后者显然有改进的地方
8.LB会话保持,原生的是不支持的,需要第三方,还有一致性hash,挺有意思的