aristark

aristark 查看完整档案

填写现居城市湘潭大学  |  会计学 编辑麻袋理财  |  IT管培 编辑填写个人主网站
编辑

If you ignore it, you’re likely to get bitten.

个人动态

aristark 赞了文章 · 2017-06-19

[译] Docker如何使用Linux iptables 和 Interfaces管理容器网络

我使用docker至今已有一段时间了,与绝大部分的人一样,我被docker强大的功能和易用性深深的折服。简单方便是docker的核心之一,它强大的功能被抽象成了非常简单的命令。当我在使用和学习docker的时候,我很想知道docker在后台都做了一些什么事情,特别是在网络这一块(我最感兴趣的一块)

我找到了很多关于创建和操作容器网络的文档,但是关于docker如何使网络工作的却没有那么多。 Docker广泛使用linux iptables和网桥接口,这篇文章是我如何用于创建容器网络的总结,大部分信息来自github上的讨论,演示文稿,以及我自己的测试。文章结尾我会给出我认为非常有用的资料链接。

我写这篇文章使用的是docker 1.12.3,但这不是作为对docker网络的全面描述,也不作为docker网络的介绍。我只希望这篇文章能给大家开拓视野,也非常感谢所有对文章错误,缺失的反馈和批评。

Docker网络概览

Docker的网络建立在允许任何一方编写自己的网络驱动程序的容器网络模型(CNM)之上。这允许不同的网络类型可用于在docker引擎上运行的容器,并且容器可以同时连接到多个网络。除了各种第三方网络驱动程序可用,docker自带四个内置网络驱动程序:

  • Bridge: 这是启动容器的默认网络。通过docker主机上的网桥接口实现连接。 使用相同网桥的容器有自己的子网,并且可以相互通信(默认情况下)。

  • Host:这个驱动程序允许容器访问docker主机自己的网络空间(容器将看到和使用与docker主机相同的接口)。

  • Macvlan:此驱动程序允许容器直接访问主机的接口或子接口(vlan)。 它还允许中继链接。

  • Overlay:此驱动程序允许在运行docker的多个主机(通常是docker群集群)上构建网络。 容器还具有自己的子网和网络地址,并且可以直接相互通信,即使它们在不同的物理主机上运行。

Bridge和Overlay可能是最常用的网络驱动程序,在本文和下一篇文章中我将主要关注这两个驱动程序。

Docker Bridge 网络

在docker主机上运行的容器的默认网络是。 Docker在首次安装时创建一个名为“bridge”的默认网络。 我们可以列出所有docker网络来查看此网络 docker network ls

$ docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
3e8110efa04a        bridge              bridge              local
bb3cd79b9236        docker_gwbridge     bridge              local
22849c4d1c3a        host                host                local
3kuba8yq3c27        ingress             overlay             swarm
ecbd1c6c193a        none                null                local

要检查其属性,运行docker network inspect bridge

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "3e8110efa04a1eb0923d863af719abf5eac871dbac4ae74f133894b8df4b9f5f",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Containers": {},
        "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": {}
    }
]

你还可以使用docker network create命令并指定选项--driver bridge创建自己的网络,例如
docker network create --driver bridge --subnet 192.168.100.0/24 --ip-range 192.168.100.0/ 24 my-bridge-network创建另一个网桥网络,名称为“my-bridge-network”,子网为192.168.100.0/24

Linux 网桥接口

docker创建的每个网桥网络由docker主机上的网桥接口呈现。、 默认桥网络“bridge”通常具有与其相关联的接口docker0,并且使用docker network create命令创建的每个后续网桥网络将具有与其相关联的新接口。

$ ifconfig docker0
docker0   Link encap:Ethernet  HWaddr 02:42:44:88:bd:75
          inet addr:172.18.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

要找到与你创建的docker网络关联的linux接口,可以使用ifconfig列出所有接口,然后找到你指定了子网的接口,例如,我们想查看我们之前创建的网桥接口my-bridge-network 我们可以这样:

$ ifconfig | grep 192.168.100. -B 1
br-e6bc7d6b75f3 Link encap:Ethernet  HWaddr 02:42:bc:f1:91:09
          inet addr:192.168.100.1  Bcast:0.0.0.0  Mask:255.255.255.0

linux桥接接口与交换机的功能类似,因为它们将不同的接口连接到同一子网,并根据MAC地址转发流量。 我们将在下面看到,连接到网桥网络的每个容器将在docker主机上创建自己的虚拟接口,并且docker引擎将同一网络中的所有容器连接到同一个网桥接口,这将允许它们与彼此进行通信。 您可以使用brctl获取有关网桥状态的更多详细信息。

$ brctl show docker0
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02424488bd75       no

一旦我们有容器运行并连接到这个网络,我们将看到interfaces列下面列出的每个容器的接口。 并且在桥接器接口上运行流量捕获将允许我们看到同一子网上的容器之间的相互通信。

Linux 虚拟网络接口(veth)

容器网络模型(CNM)允许每个容器具有其自己的网络空间。 从容器内部运行ifconfig将显示容器内部的网络接口:

$ docker run -ti ubuntu:14.04 /bin/bash
root@6622112b507c:/#
root@6622112b507c:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:12:00:02
          inet addr:172.18.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe12:2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:9 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:766 (766.0 B)  TX bytes:508 (508.0 B)


lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

然而,上面看到的eth0只能从那个容器中可用,而在Docker主机的外部,docker会创建一个与其对应的双虚拟接口,并作为到容器外的链接。 这些虚拟接口连接到上面讨论的桥接器接口,以便于在同一子网上的不同容器之间的连接。

我们可以通过启动连接到默认网桥的两个容器来查看此过程,然后查看docker主机上的接口配置。

在运行启动任何容器之前,docker0 桥接接口没有连接的接口:

$ sudo brctl show docker0
$ sudo brctl show docker0
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02424488bd75       no

然后我从ubuntu:14.04 镜像启动2个容器

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
a754719db594        ubuntu:14.04        "/bin/bash"         5 seconds ago       Up 4 seconds                            zen_kalam
976041ec420f        ubuntu:14.04        "/bin/bash"         7 seconds ago       Up 5 seconds                            stupefied_easley

您能马上看到现在有两个接口连接到docker0网桥接口(每个容器一个)

$ sudo brctl show docker0
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02424488bd75       no              veth2177159
                                                        vethd8e05dd

从其中一个容器ping到google,然后从docker主机对容器的虚拟接口进行流量捕获,将显示容器流量

$ docker exec a754719db594 ping google.com
PING google.com (216.58.217.110) 56(84) bytes of data.
64 bytes from iad23s42-in-f110.1e100.net (216.58.217.110): icmp_seq=1 ttl=48 time=0.849 ms
64 bytes from iad23s42-in-f110.1e100.net (216.58.217.110): icmp_seq=2 ttl=48 time=0.965 ms

ubuntu@swarm02:~$ sudo tcpdump -i veth2177159 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth2177159, link-type EN10MB (Ethernet), capture size 262144 bytes
20:47:12.170815 IP 172.18.0.3 > iad23s42-in-f14.1e100.net: ICMP echo request, id 14, seq 55, length 64
20:47:12.171654 IP iad23s42-in-f14.1e100.net > 172.18.0.3: ICMP echo reply, id 14, seq 55, length 64
20:47:13.170821 IP 172.18.0.3 > iad23s42-in-f14.1e100.net: ICMP echo request, id 14, seq 56, length 64
20:47:13.171694 IP iad23s42-in-f14.1e100.net > 172.18.0.3: ICMP echo reply, id 14, seq 56, length 64

同样,我们可以从一个容器平到另一个容器。
首先,我们需要获取容器的IP地址,这可以通过在容器中运行ifconfig或使用docker inspect命令检查容器来完成:

$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' a754719db594 
172.18.0.3 

然后我们从一个容器ping另一个容器

$ docker exec 976041ec420f ping 172.18.0.3
PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.070 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.053 ms

要从docker主机看到这个流量,我们可以在对应于容器的任何一个虚拟接口上捕获,或者我们可以在桥接口(在这个实例中为docker0)上捕获,显示所有的容器间通信子网:

$ sudo tcpdump -ni docker0 host 172.18.0.2 and host 172.18.0.3
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes
20:55:37.990831 IP 172.18.0.2 > 172.18.0.3: ICMP echo request, id 14, seq 200, length 64
20:55:37.990865 IP 172.18.0.3 > 172.18.0.2: ICMP echo reply, id 14, seq 200, length 64
20:55:38.990828 IP 172.18.0.2 > 172.18.0.3: ICMP echo request, id 14, seq 201, length 64
20:55:38.990866 IP 172.18.0.3 > 172.18.0.2: ICMP echo reply, id 14, seq 201, length 64
定位一个容器的vet接口

没有直接的方法来找到docker主机上的哪个veth接口链接到容器内的接口,但是在各种docker论坛和github中讨论了几种方法。在我看来最简单的是以下(基于这个解决方案做了稍微的修改),这也取决于ethtool在容器中可访问

例如:我的系统上运行了3个容器

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
ccbf97c72bf5        ubuntu:14.04        "/bin/bash"         3 seconds ago       Up 3 seconds                            admiring_torvalds
77d9f02d61f2        ubuntu:14.04        "/bin/bash"         4 seconds ago       Up 4 seconds                            goofy_borg
19743c0ddf24        ubuntu:14.04        "/bin/sh"           8 minutes ago       Up 8 minutes                            high_engelbart

首先我运行如下命令来获得peer_ifindex

$ docker exec 77d9f02d61f2 sudo ethtool -S eth0
NIC statistics:
     peer_ifindex: 16

然后在docker主机上,通过peer_ifindex 找到接口名称

$ sudo ip link | grep 16
16: veth7bd3604@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default

所以,在目前的情况下,接口名称是:veth7bd3604

iptables

Docker使用linux iptables来控制与它创建的接口和网络之间的通信。 Linux iptables由不同的表组成,但我们主要关注两个:filternat。过滤器是网络或接口的流量的安全规则表,用于允许或拒绝IP地址,而nat包含负责屏蔽IP地址或端口的规则。Docker使用nat允许桥接网络上的容器与docker主机之外的目的地进行通信(否则指向容器网络的路由必须在docker主机的网络中添加)

iptables:filter

iptables中的表由对应于处理docker主机上的数据包的不同条件或阶段的不同链组成。默认情况下,过滤器表具有3个链:用于处理到达主机并且去往同一主机的分组的输入链,用于发送到外部目的地的主机的分组的输出链,以及用于进入主机但具有目的地外部主机。每个链由一些规则组成,这些规则规定对分组采取一些措施(例如拒绝或接受分组)以及匹配规则的条件。 顺序处理规则,直到找到匹配项,否则应用链的默认策略。 也可以在表中定义自定义链。

要查看过滤器表中链的当前配置的规则和默认策略,可以运行iptables -t filter -L(或iptables -L,如果未指定表,则默认使用过滤器表)

$ sudo iptables -t filter -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:domain
ACCEPT     udp  --  anywhere             anywhere             udp dpt:domain
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:bootps
ACCEPT     udp  --  anywhere             anywhere             udp dpt:bootps
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
DOCKER-ISOLATION  all  --  anywhere             anywhere
DOCKER     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
DOCKER     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
DOCKER     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
DROP       all  --  anywhere             anywhere
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
Chain DOCKER (3 references)
target     prot opt source               destination
Chain DOCKER-ISOLATION (1 references)
target     prot opt source               destination
DROP       all  --  anywhere             anywhere
DROP       all  --  anywhere             anywhere
DROP       all  --  anywhere             anywhere
DROP       all  --  anywhere             anywhere
DROP       all  --  anywhere             anywhere
DROP       all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere

突出显示的是不同的链,以及每个链的默认策略(没有自定义链的默认策略)。 我们还可以看到Docker已经添加了两个自定义链:DockerDocker-Isolation,并且在Forward链中插入了以这两个新链作为目标的规则。

Docker-isolation chain

Docker-isolation包含限制不同容器网络之间的访问的规则。 要查看更多详细信息,请在运行iptables时使用-v选项

$ sudo iptables -t filter -L -v
….
Chain DOCKER-ISOLATION (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DROP       all  --  br-e6bc7d6b75f3 docker0  anywhere             anywhere
    0     0 DROP       all  --  docker0 br-e6bc7d6b75f3  anywhere             anywhere
    0     0 DROP       all  --  docker_gwbridge docker0  anywhere             anywhere
    0     0 DROP       all  --  docker0 docker_gwbridge  anywhere             anywhere
    0     0 DROP       all  --  docker_gwbridge br-e6bc7d6b75f3  anywhere             anywhere
    0     0 DROP       all  --  br-e6bc7d6b75f3 docker_gwbridge  anywhere             anywhere
36991 3107K RETURN     all  --  any    any     anywhere             anywhere

您可以在上面看到一些删除规则,阻止任何由docker创建的桥接接口之间的流量,从而确保容器网络不能通信。

icc=false

可以传递到docker network create命令的选项之一是com.docker.network.bridge.enable_icc,它代表容器间通信。 将此选项设置为false会阻止同一网络上的容器彼此通信。 这是通过在前向链中添加一个丢弃规则来实现的,该丢弃规则匹配来自与去往同一接口的网络相关联的桥接器接口的分组。

举个例子,我们用以下命令创建一个新的网络

docker network create --driver bridge --subnet 192.168.200.0/24 --ip-range 192.168.200.0/24 -o "com.docker.network.bridge.enable_icc"="false" no-icc-network
$ ifconfig | grep 192.168.200 -B 1
br-8e3f0d353353 Link encap:Ethernet  HWaddr 02:42:c4:6b:f1:40
          inet addr:192.168.200.1  Bcast:0.0.0.0  Mask:255.255.255.0

$ sudo iptables -t filter -S FORWARD
-P FORWARD ACCEPT
-A FORWARD -j DOCKER-ISOLATION
-A FORWARD -o br-8e3f0d353353 -j DOCKER
-A FORWARD -o br-8e3f0d353353 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i br-8e3f0d353353 ! -o br-8e3f0d353353 -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A FORWARD -o br-e6bc7d6b75f3 -j DOCKER
-A FORWARD -o br-e6bc7d6b75f3 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i br-e6bc7d6b75f3 ! -o br-e6bc7d6b75f3 -j ACCEPT
-A FORWARD -i br-e6bc7d6b75f3 -o br-e6bc7d6b75f3 -j ACCEPT
-A FORWARD -o docker_gwbridge -j DOCKER
-A FORWARD -o docker_gwbridge -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker_gwbridge ! -o docker_gwbridge -j ACCEPT
-A FORWARD -o lxcbr0 -j ACCEPT
-A FORWARD -i lxcbr0 -j ACCEPT
-A FORWARD -i docker_gwbridge -o docker_gwbridge -j DROP
-A FORWARD -i br-8e3f0d353353 -o br-8e3f0d353353 -j DROP

iptables:nat

NAT允许主机更改数据包的IP地址或端口。在这种情况下,它用于屏蔽源IP地址来自docker网络(例如172.18.0.0/24子网中的主机),目的地为容器外,位于docker主机的IP地址之后的数据包。此功能由com.docker.network.bridge.enable_ip_masquerade选项控制,可以在docker network create(如果未指定,则默认为true)命令中使用。

你可以在iptables的nat表中看到此命令的效果

$ sudo iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL


Chain INPUT (policy ACCEPT)
target     prot opt source               destination


Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere            !127.0.0.0/8          ADDRTYPE match dst-type LOCAL


Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.18.0.0/16        anywhere
MASQUERADE  all  --  192.168.100.0/24     anywhere
MASQUERADE  all  --  172.19.0.0/16        anywhere
MASQUERADE  all  --  10.0.3.0/24         !10.0.3.0/24


Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere

postrouting链中,您可以看到在与自己网络外部的任何主机通信时,通过应用伪装操作创建的所有docker网络。

总结

  • 网桥网络在docker主机上具有对应的linux网桥接口,其作为layer2交换机,并且连接在同一子网上的不同容器。

  • 容器中的每个网络接口在Docker主机上具有在容器运行时创建的对应虚拟接口。

  • 桥接接口上来自Docker主机的流量捕获等效于在交换机上配置SPAN端口,可以在该网络上查看所有集群间通信。

  • 在虚拟接口(veth- *)上来自docker主机的流量捕获将显示容器在特定子网上发送的所有流量

  • Linux iptables规则用于阻止不同的网络(有时网络中的主机)使用过滤器表进行通信。 这些规则通常添加在DOCKER-ISOLATION链中。

  • 容器通过桥接接口与外部通信,其IP被隐藏在docker主机的IP地址后面。 这是通过向iptables中的nat表添加规则来实现的。

相关连接

Docker networking concepts

Deep dive into Docker 1.12 Networking

Docker container networking user guide

Linux iptables overview

原文:docker-networking-internals-how-docker

云盟认证成员】: 超儿哥

查看原文

赞 5 收藏 16 评论 0

aristark 关注了问题 · 2016-08-20

导入org.springframework.beans.annotation.Autowired 报错

package hello;

import org.springframework.beans.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MessagePrinter {

    final private MessageService service;

    @Autowired
    public MessagePrinter(MessageService service) {
        this.service = service;
    }

    public void printMessage() {
        System.out.println(this.service.getMessage());
    }
}

依赖的包已经在maven dependencies中了,为什么还是报错呢?

关注 3 回答 3

aristark 回答了问题 · 2016-08-20

导入org.springframework.beans.annotation.Autowired 报错

有时候就是IDE反应慢的问题吧,去试一下IDEA?

关注 3 回答 3

aristark 关注了问题 · 2016-07-10

shiro 注解加在controller类级别无效,加在方法级别正常。

shiro @RequiresPermissions和@RequiresRoles注解加在controller类级别无效,加在方法级别正常。

关注 6 回答 4

aristark 关注了问题 · 2016-07-10

注解模式一般用在什么地方,有什么优势?

在看三大框架Spring,struts,hibernate,提到注解模式,求指点,谢谢

关注 3 回答 2

aristark 回答了问题 · 2016-07-10

注解模式一般用在什么地方,有什么优势?

既然你都看了srping了就该体会到用注解是多么爽的一件事儿了,简化开发,让代码更优雅嘛

关注 3 回答 2

aristark 赞了文章 · 2016-03-31

我的自学编程故事

前言

之前有人留言说想看我的所谓奋斗史,前天的这篇调查「以产品思维去做微信公众号」也显示这部分人比例还不少,今天突然想到我在2年前在知乎回答过一个「如何自学Android编程」的问题,今天特地去重新看了一遍,2年多的时间再次看这篇文章勾起了我不少的回忆,很感谢当初的回答,记录了我人生的一个阶段,给我留下了人生一笔宝贵的财富。虽然可能有部分人看过,但是我觉得还是有必要搬到这里,给那些还在迷茫的人一些激励。

问题:如何自学 Android 编程?

想自学android编程,从何入手。2000年本科入学,期间学过c c++ 数据结构,之后就再也没有用过。本人数学专业出身。求大牛们指点迷津。

我的回答

泻药。我想我是有资格来回答你的问题的,我算是彻底的从编程白痴一步步学习过来的。

之前写了一篇博客「Android学习之路」被疯狂转发,带起了一阵学习Android的大风,我不晓得具体影响了多少人,但是这篇博客已经成为Android新手学习必备的资料。很多人问我是怎么一路过来的,姑且借这个机会说说我的故事吧。

内容有点长,大家姑且当做励志小说来看吧,对这些经历不感兴趣的可以直接看最后「如何自学Android」部分。

大学

大学上的是全国二流本科,专业主要就是数学专业。大学的总结就是:逃课、挂科、篮球、泡妞、Dota。

目前为止可以说我的大学是我这辈子过的最爽的时期,大一开始就逃课,好点的理由是我对大学那些课程完全没兴趣,其实本质上是因为高考太压抑了,大学之后没人管了,开始无止境的放纵自己。毫不夸张的说大学四年上的课应该没有逃的课多。逃课最直接的影响就是挂科,从大一挂到大三,专业课数学分析特么每年都挂,后来听说补考考不过要重修,还得交钱,这特么让家里人知道还了得,于是发愤图强,最终补考每门都以优秀的成绩过了。

相信英语四级应该是大学必考吧,英语四级这辈子我都忘不了,我考了三次才过,每次考试说是裸考都褒奖了,大学英语全是吃高中的底子,第一次没过,少了十几分,第二次他妈睡觉睡过了,就没有去考,现在想想当时的自己真是脑残,终于在第三次勉强过了。现在想想幸亏当时过了,之后毕业找工作时英语四级是最低要求。做编程这行,英语真的挺重要的,大学的时候一定得想办法把四级给过了。

现在想想大学和编程唯一相关的课程就是C语言了,但是对于我们专业是属于全校公开课,最终这门课虽然考过了,但是连i++ 和++i我都弄不明白,可想而知你们应该能猜到我是怎么过的。

由于高中就开始打篮球,大学时毫无疑问就入了学院篮球队,而且担任队长,所以大一、大二过来基本就是一大半是在篮球场度过的,也是因为篮球征服了我现在的老婆。大二下学期周围的人都在玩Dota,于是也开始了游戏之路,篮球也基本放弃了,那时候玩的疯狂,记得最长的一次和同学在网吧连续包夜两星期,不过白天是回来睡觉的,不然估计早猝死在网吧了。

转眼间大四了,这一年终于在我老婆的监督下不再挂科了,而且在毕业前的最后一次评比竟然拿到了三等奖学金,真是受宠若惊啊,后来钱也被狐朋狗友们拿去腐败了。周围的同学都在准备考研,我却唯独对考研没兴趣,自始至终都没有为考研花过一分钱,买过一本书。后来证明我的决定是正确的,周围的小伙伴放弃的放弃,考不上的考不上,考上的后面也没见混的有多好,当然如果能考上好的学校还是很有帮助的。

毕业

又一转眼毕业了,去参加了学校几个招聘会也是醉了,全是没听过的小企业,一去参观都是各种厂房,根本不需要任何学历,是个人出点力都可以干的那种,要么就是各种电话销售。。。那时候真的有点急了,大学四年,毕业一份像样的工作都找不到,回去怎么面对乡亲父老。

三月份的时候一家IT测试培训机构来做宣讲会(具体什么名字我就不说了,免得认为是广告),听完之后就说培训之后包就业,培训地点在大上海的最中心,当然是自费,但是可以工作之后分期付款。那时候哪知道测试是干嘛的,更不懂IT是干嘛的,回来就查了下这个机构确实存在,网站也有的,知道不是骗子遍决定来上海试一试。当时下定决定要来上海培训之后,几个狐朋狗友毫不犹豫的跟着我一起来了,总共应该是7个人,决定之后没几天便毅然而然的背着大包小包来到上海,我们之中一部分是一次性付费,另一部分是分期。后来证明我们当时来上海的决定是正确的。

培训之路

这里并不是宣传大家去培训,只不过被工作所逼,学校不好,大学也没学啥真本事,稍微有点出路都不会去培训!

培训的日子还是蛮轻松的,一周就上四天课,朝九晚五,毕竟大家家庭都不富裕,家里辛苦攻读了四年大学,毕业还要再交钱去培训,打心里大家都很愧疚。一开始卯足了劲要好好学习,但是坚持了没多久就有点放松了,但是好歹也是都有学习的。

培训的时候算是接触了点编程吧,课程有C语言、Oracle、Linux,然后其他都是软件测试的理论,其实都是非常基础的东西,算是带你入行吧,Orace就学习一些基本SQL语句,Linux就学了几个命令,但是起码知道原来电脑可以干这么多事,之前还一直以为电脑就是用来看电影、聊天、打游戏的呢。现在回头看给我一周的时间我可以看完整个培训课程,但是当时的培训周期要四个月。

第一份工作「SunNet」

7月份拿到毕业证书就直接来了上海,四个月的时间对于我来说实在太漫长了,我等不及了,于是在课程还有最后一个月的时候我提前出来单独找工作,当时培训老师说工作后的工资大多是3500左右,我觉得我学的比别人好,所以必须要比别人高,做好了长期准备面试的过程。没想到面试第一家就直接录取了(后来才知道原来是公司的测试怀孕在家,急需测试),公司名叫SunNet,中文名山诺,工资4000。收到offer的时候欣喜若狂!

真正接触编程

之后便在SunNet正式入职,工作就是测试,但是就在SunNet我才真正的接触了什么是编程。SunNet是一家小型外企,总部在休斯顿,上海这边主要是研发部。研发人员总共不到10人,主要业务是以web为主,使用语言是.NET。当然我的职责就是web测试了,工作倒是没什么难点,主要测试就我一个,一进来就有好几个项目等着我测试,还好自己是数学专业,逻辑思维能力还不错,加加班总算能按时完成任务。

三个月的时间顺利转正,之后的某一天偶然得知公司一开发人员工资比我高2000,其实也就比我早毕业一年而已,才知道原来开发和测试工资差距这么大,于是当时就下定决心要转行做开发,所以说起我从测试转行开发的原因是因为钱,一开始说因为兴趣坚持下来的都是扯淡。。。

编程之路

由于在公司我的主要任务仍然是测试,任务也挺多,基本每天要9点能到家。下定决心之后只能利用业余时间来学习,由于公司主要业务是.NET,很自然我一开始学习的方向便是.NET,后来才了解到.NET是基于C#的,学习.NET之前必须要先学习C#,于是像公司借了一本C#学习资料,就这样学习了大概两周吧,还仔仔细细做了一大堆笔记。之后觉得语法学的差不多了,便又借了本.NET的书开始学习web开发。

但是web开发远没有学习一门语言这么简单,后来才知道web开发涉及到html, css, javascript, ajax, database等,范围太广了,于是为了效率,我只能每一项掌握住基础,于是网上搜索找到 w3school 在线教程 这个好东西,里面的东西都是基础的,我把教程整理到手机里,每天上下班的地铁上,吃饭的时候,回家的时候等等利用一切时间来学习,也是差不多大概2周的时间掌握了html, css, js的一点基础,接下来准备正式学习.NET的时候,被我们老大知道我正在学习开发,我们老大叫Aaron,是一位典型的程序员,比较胖,人品超好,知道后不建议我学习.NET,说.NET出来都十来年了,已经很成熟了,很多人在学校都有.NET教程,现在学习已经比别人晚了。现在移动开发刚崭露头角,也没多少人会,建议我直接学习移动开发。真是一句点醒了我啊!!

移动开发

听了老大的建议,于是放弃了.NET,开始准备学习移动开发。那时候是12年初,整个移动互联网也是刚起步阶段,iPhone4还是非常火的时候,毫无疑问我把目标锁定在了iPhone开发上。

这次有经验了,知道了iPhone开发首先肯定得有一门编程语言的,了解到是Objective-C,于是又从公司借了一本OC的书(公司书很多,老大每月都会买来好多书,虽然看的人很少),又花了将近一周的时间来了解了基本语法,因为之前学了C#,这次没完全看完的情况下想直接上手实践。正打算学习的时候,才知道iOS开发必须得在mac上才可以,也就是说我得自己有台mac电脑,虽然当时公司是有的,但是回家的时候不能继续学习练习,肯定影响效率的,一台mac对于当时的我简直是奢侈品,虽然现在也依然是奢侈品,但当时根本不可能买得起。于是只有放弃iPhone开发转向Android(后来才知道有虚拟机这东西。。。)

Android学习之路
几经波折,终于才确定自己的方向,这期间已经耽误了不少时间,走了不少弯路了,但是仍然阻挡不住我转行开发的脚步。公司关于Android的书竟然没有,于是我自己亲自买了一本Android书籍,叫「疯狂Android讲义」,作者是李刚!!不说这本书有多好吧,这本书比较厚,非常厚,所以讲的一些基础比较细,新手按照上面的完全可以看懂,现在不知道这本书内容更新了没,之前内容对于现在来说有点老。这个时候应该是12年的3月份,说起来也巧,这个月公司刚好接了个移动端项目,iOS有人来做,但是公司上下没有会Android开发的,其他人都有自己的项目在忙,老大看出我的兴趣,就问我想不想试一下,我当然欣然同意了,只此就开始正式的边做边学习Android。

那个时候的测试任务仍然很重,于是我只能挤出时间来做这个项目,基本是每晚以及周末都会自动加班来完成这个项目,现在看来那个项目真是太简单不过了,快的话一周都不要就可以搞定了,但在当时我摸爬滚打了一个多月总算搞定了。

做完这个项目自己有了点信心,但当时自己的主要任务还是测试,自己开发的时间是少之又少,于是下定决心离职找一个专门做Android的工作。说明原因之后老大很理解,也是建议我想做开发的话就放手去找一个专门做开发的职位,这样进步会比较快。

第一份工作能遇到我的老大Aaron,真是我的福分!

薄荷

12年4月底离职之后,我便开始在家里准备面试,投简历,由于自己的基础很薄弱,之前的Android经验也非常少,那时候说是招聘Android职位,但是很多公司面试基本全是Java内容,而且我能感受到工作内容和Android相关的也比较少。来薄荷面试的时候感觉很好,包括CTO、CEO的面试都感觉不错,果然没几天就收到offer,来之不易的机会,非常珍惜。

12年5月中入职薄荷,你们以为我到了薄荷之后Android之路就一帆风顺?错了,路更艰难。我是薄荷入职的第一位Android工程师,薄荷在那时候只做iOS平台的App,事实证明这种做法不可取,包括BOSS后面也亲自说投资人来投资的时候一看数据这么少,后面做了Android平台之后用户数据才慢慢上来,毕竟还是Android用户基数大。

来薄荷的第一个任务是做联想TV的一个视频App,很蛋疼,折腾联想的SDK,遥控器等,好歹App功能比较简单,试用期间完成的还不错,就这样顺利转正了。转正之后做了第一个Android App,叫减肥食物库,功能与页面全部Copy iOS版的App。这个时候一大堆没做过的东西全部涌来了,网络库、http、api对接、组件定制等等,那个时候没有像现在各种资料、开源库一大堆,公司也没人能给指导,只有硬着头皮慢慢来,为了能把进度赶上去,上线前夕曾经连续在公司通宵三天三夜,累了就在公司趴一会,这辈子就经历过这一次,虽然项目最终也拖延,但总算在接受范围之内上线,这在当时是第一个试水Android平台的App,可惜的是上线之后表现不好,没有达到预期的效果。

Ruby、Rails

这个App试水表现不好之后,公司决定暂停Android业务,主攻iOS平台。我的位置就变得很尴尬,CTO找我谈话希望我能转做Ruby,Android业务会暂停,我们后端的api全部是基于Ruby语言的,虽然当时心里有点不情愿,但是没办法,为了不丢掉工作我只得硬着头皮做下去,于是就开启了我的Ruby之旅。

学习Ruby以及Rails是我目前为止遇到的最大的困难。Ruby是一种动态语言,Ruby On Rails是一种流行快速开发web的框架。学习ruby不同地方在于要学习Linux,代码基本全是在终端进行,没有IDE,没有编译检查,习惯各种命令行。这对于一个刚开始进行开发的人来说真的是太困难了。那个时候从内心也有抵触,学习领悟能力也超差,我能感觉到公司领导层对我已经不是很满意了,现在想想那时候公司没能开除我真是得感谢我们的CTO,一个很有耐心、敢于给新人机会的人。中间遇到非常大的困难,还好得到薄荷ruby工程师小伙伴们的帮助,一直鼓励我,遇到问题总是不厌其烦,在基础、经验、能力不行的情况下,我总得克服这种困难,让别人看出我的态度。于是学习Ruby、Rails的那段时间我每天都是做最后一班公交车回去,我记得好像是11:40. 这个状态持续一个月,包括周末,从未间断。

这一个月我在公司的主要任务就是学习,没有给安排任何事情,偶尔帮同事修修电脑,调下电话线,为此我非常感谢薄荷,感谢我的CTO Vincent,恐怕这在任何一家公司都不能这样给一个人单独的学习时间吧。一个月之后,学习的终于有点起色,开始实际做Rails项目,做后台,写api,虽然效率很低,代码很烂,但是在做Ruby的过程跟着我们CTO学了不少设计上的东西,面向对象的思想,设计模式等都在这个阶段接触的,自此我已经习惯各种终端操作,熟悉一些Linux基本命令,喜爱Ubuntu,参加了Ruby Conf 2012大会,喜欢Sublime,接触Git,GitHub,搭建了自己的Blog。我感谢做Ruby的这段时间,他让我接触了不一样的世界,心态完全变了,变得更开放,更乐于分享。

回归Android

做ruby的这段时间很是快乐,付出了这辈子最艰辛的努力,换来了点小小成果,我喜欢ruby语言,到现在为止都认为ruby是最优雅的语言。但是Android在我心里一直都有一个位置,从未放弃过。直到13年5月的时候,这个时候Android份额近乎疯狂的增长,公司不得不开始注意Android市场,于是公司打算推出Android版app,于是作为公司唯一的Android程序员,CTO再次征求我的意见,问我想继续做ruby还是做Android,虽然我更喜欢ruby语言,但是从心底我更喜欢客户端开发,和语言无关,客户端是用户最能感知的一部分,做出的东西更有成就感一点,我就在等待着这个机会,这一刻我毫不犹豫的选择继续做Android。

回归Android开发的我一个人开发新的App,单枪匹马,经过之前的一系列挫折,突然感觉再没有更困难的了,一个人摸索架构、克服难题,App一推出便一直快速疯狂的增长,目前薄荷App总用户数已经破千万,公司也顺利融资,业务发展越来越好。

后话

由于一个人一步步过来,我深知这其中的不易,之后自己一直坚持写博客,除了自己的积累外,还希望帮助更多的人,所以现在你们可以看到不管是我的GitHub,博客,微博还是微信公众账号都很乐于分享,希望自己走过的这些弯路你们能少经历,虽然最终我也到达了终点,但是如果能有直路可直达最好不过了。

总结

上面啰啰嗦嗦的叙述我的这些陈年经历,看似和题主的问题无关,其实有很大关系:

  • 1.如果你是非科班毕业想要转行编程,看了我的经历你一定很有信心与动力,我能,你一样能,何况我中间走了这么多弯路;

  • 2.如果你接触过一些数据结构、编程语言之类的,想要学习编程,想题主这种,你更应该有信心,你比那些非科班的人已经多了一些基础,你要做的只是坚定信念,坚持走下去;

  • 3.不要感慨上天的不公平,一路走来很悲催,走了太多的弯路,遇到了太多的困难,都是我这辈子难以忘记的坎坷,但同时我是幸运的,我的前任老大Aaron以及现任CTO都深深的影响着我后来的职业发现,我在薄荷的一些小伙伴们对我的帮助,我女朋友无数个日夜的理解与支持,上帝在给你制造坎坷的时候总会对你有些眷顾;

  • 4.如果你准备或已经在做开发的人员,没有信心或动力,又或是对未来迷茫的时候,姑且当做一篇励志小说来看,你要做的就是做好你当下在做的事,相信你的付出一定能有所收获;

建议

评论里有人说是在校生,想要给点建议,那就以一个反例的身份给出事后的一些建议吧:

  • 1.每天花1小时看书,难的不是1小时,难的在坚持;

  • 2.最好不要挂科,因为挂了你还是得花时间补回来;

  • 3.英语对编程来说很重要,试着习惯英语阅读;

  • 4.数据结构和算法要学好,这算是对编程来说最重要的课了,以后不见得能用得上,但用的时候起码你有印象;

  • 5.大三或者大四一定要去实习,对于编程来说经验还是比较重要的,实习经历比你那些理论要有效的多;

  • 6.找个女朋友吧,不是说工作了你就找不到了,而是工作之后从事编程很少有时间和精力去接触别的女孩,所以可以的话在毕业前找个可以同甘苦的女朋友吧;

  • 7.第一份工作很重要,因为你会受一些周围同事、公司氛围的影响,我为自己遇到Aaron、Vincent而感到幸运,他们俩是我目前为止最尊重的两个人;

如何自学Android编程

最后才应该是题主最需要的:

  • 1.「Android学习之路」前面说过我的这篇博客是Android新手必备资料;

  • 2.书不在多,适合你就好,这里推荐一本Android和两本Java书籍吧;

    • 「第一行代码」:郭霖大神的著作,CSDN博客专家;

    • 「Thinking In Java」:Java经典书籍,不必说;

    • 「Effective Java」:Java进阶必备书籍;

  • 3.有些人一开始看书也有困难,没关系,那就去网上看视频,结合视频和书一起看,边看边敲,推荐下Mars老师视频,包括java基础以及Android篇都有:

    • Java4Android.zip_免费高速下载

    • 极客学院的视频

    • 慕课网的视频
      口味不同的人可去各大网站自行搜索适合自己的教学视频。

  • 4.多去混GitHub,目前最流行的开源社区,多参与开源项目,慢慢试着读牛人们的代码,时机到的时候自己也可以为开源社区贡献力量,可以这样说在GitHub上认真混一年,比你在大学四年学的东西要多的多;

  • 5.关注一些Android界不错的博客:

  • 6.善于利用搜索引擎,从用Google做起,学会英文关键字搜索,有问题第一时间想到去搜索而不是去请教别人;

  • 7.关注我的微信公众号:AndroidDeveloper「googdev」,目前应该算是国内最有影响力的个人Android公众号了,我会经常分享一些Android方面的原创干货,希望能继续干着自己喜欢的事,帮助更多的人。

大家都有了以上相同的教程,但是人和人之间的区别在于:别人有颗更强大的内心,可怕的是比你聪明的人比你还要更努力!!

当你下定决心,准备前行的时候,剩下的只有坚持了!

查看原文

赞 9 收藏 25 评论 1

aristark 赞了问题 · 2016-03-31

hibernate search jpa 索引库 本地能够正常搜索,部署到服务器上就搜索不到结果了?

hibernate search jpa 索引库 本地能够正常搜索,部署到服务器上就搜索不到结果了

关注 1 回答 0

aristark 赞了问题 · 2016-03-31

hibernate search jpa 索引库 本地能够正常搜索,部署到服务器上就搜索不到结果了?

hibernate search jpa 索引库 本地能够正常搜索,部署到服务器上就搜索不到结果了

关注 1 回答 0

aristark 赞了文章 · 2016-03-31

[译]Java和Python——应该先学习哪种编程语言

Java和Python是目前两种非常流行且功能强大的编程语言。初级程序员常常感到困惑,最常被问到的问题就是应该学习Java还是Python,Python是不是容易上手,应该推荐给初学者学习什么样的编程语言等等。首先因为我是一个Java开发者,所以我会有偏见,我会建议你用Java入门,然后学习Python,但如果你向一个Python开发者问这个问题,你可能会得到相反的答案。我有很多很好的理由证明为什么一个程序员应该学习Java。最重要的一个原因,你会发现其实博客就是一个强大的Java社区,它能够帮助你更好地发展你的Java事业。你可以问一些初级程序员,在用Eclipse调试Java程序时遇到的无论是简单的还是高级的问题,在Java社区总会有一些人会时刻准备着帮你回答。这便是为什么Stack Overflow上全是Java问题的原因。顺便一提Python不再是程序语言界的黄毛丫头了,它已经成长起来并可以和像Java、C++一样的主流语言进行竞争。

当我第一次遇到Python,我认为它是一种脚本语言,但这样说是很肤浅的。你也可以用它来进行面向对象编程。从初学者的角度出发,我总是建议选择一门简单的语言学习,因为这能很好地吸引你,同时能有强大的社区支持你的学习,现在Java和Python都符合这一特点,直到你做一些很棒的比较分析之前,你可以先不决定学习Java还是Python。值得庆幸的是,我们有一个图表,它说明了Python和Java的一些重要的区别,我确信当你仔细看完这个图表之后,你将会知道哪一门编程语言更适合你学习。

Python 和 Java 之间的区别 - 图表

Java Vs Python

作为两种不同的编程语言,Java和Python都是现在开发者非常有用的工具。在学习任何编程语言之前,你必须知道它们之间的区别。由于众多的决定性因素,简单的说Java相比Python成效不足是不严谨的。让我们通过这个图表认识差异。在这个图表中,你可以很容易地看到,Java与Python相比非常冗长。Java读取文件需要写10行代码,而Python只需要2行代码。这一点倾向了Python,因为初级程序员当然喜欢写更少的代码。第二点是关于静态和动态类型变量,在我看来,初学者需要一个强类型的语言和严格的编译器来检测初学者犯的低级错误,你可以看到Java不能在一个字符串变量中存储整数值。所以在这一点上,我把票投给了Java。

对于速度,这是企业级应用开发的关键,你可以看到,Java的执行速度比Python快,但请记住,Java需要编译然后运行,而Python不需要被编译可以直接运行。Python控制台可以直接解释成Python命令,这意味着对程序员来说更为简单。所以现在Python和Java的比分都是2比2。你同时可以看到Hello World在Java中比Python需要更多行的代码。剩下的例子你可以自己分析,但所有这些看来Java和Python的能力是相同的,没有一个会比另外一个差。作为一个Java开发人员,我可以说,以我个人的经验,你学习Java然后选择它成为你的职业是不会后悔的,当然你随时都可以学习Python然后用它来写一些实用工具,但Java终究是Java。

享受你的编程吧。

原文:http://javarevisited.blogspot...

查看原文

赞 2 收藏 7 评论 4

认证与成就

  • 获得 16 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-03-01
个人主页被 309 人浏览