Docker

Abstract

Docker是一个开源的容器引擎,可以将应用软件和应用所需的依赖打包在一起,形成一个标准化单元,类似于Java基于jvm的write once, run anywhere, docker可以实现build once, run anywhere。最近几年,微服务架构火热,而在大型企业中,微服务的运维由于服务的数量比较多而进展困难,容器化服务势在必行。服务容器化使应用程序能够从组件快速组装到发布,消除了开发和QS和IT的摩擦。容器是微服务的基石,没有容器化的微服务是没有灵魂的。docker让容器化操作变得简单,docker的出现让大型的微服务架构得以流行,而微服务的火热又促进了docker发展。

Why

一致的运行环境

开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 「这段代码在我机器上没问题啊」 这类问题。

持续交付和部署

对开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。
使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 Dockerfile 来进行镜像构建,并结合 持续集成(Continuous Integration) 系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合 持续部署(Continuous Delivery/Deployment) 系统进行自动部署。
而且使用 Dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。

更轻松的迁移

由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。

更轻松的维护和扩展

Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的 官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。

VM, Vagrant, Docker

最开始,大家就想着直接把整个系统打包再部署,即虚拟机技术,保证了系统和环境的稳定,减少了大量手工操作。但是,缺点也是有的,那就是无法自动化打包过程,于是Vargrant被做了出来,Vargrant使用了vargrantfile文件给开发者提供了定义自动化构建VM镜像的功能。但是,还是有缺点,首先,打包出来的镜像太大,包含了过多不需要使用的依赖;其次,服务启动必须等得VM的系统启动完成后才能启动服务。
为了解决VM的缺点,Docker被开发了出来,它提供了系统上的进程级别的虚拟化技术。与VM需要虚拟化整个硬件和操作系统不同,Docker是直接基于宿主机的操作系统进行虚拟化,性能几乎没有损耗。具体结构差异如下:

虚拟机:
    +------------+-------------+
    | App A      |  App B      |
    +------------+-------------+
    | Libs       |  Libs       |
    +------------+-------------+
    |Guest OS    |  Guest OS   |
    +------------+-------------+
    |       Hypervisor         |
    +--------------------------+
    |        HOST OS           |
    +--------------------------+
    |         SERVER           |
    +--------------------------+

Docker:
    +------------+-------------+
    | App A      |  App B      |
    +------------+-------------+
    | Libs       |  Libs       |
    +------------+-------------+
    |       Docker Engine      |
    +--------------------------+
    |        HOST OS           |
    +--------------------------+
    |         SERVER           |
    +--------------------------+

Docker的组成

docker主要由以下组件组成:

  • docker client
  • docker daemon
  • docker image
  • docker container
  • docker registry

Docker本身是C/S架构,C端是docker client,S端是docker daemon,docker daemon管理者docker image和docker container,docker index提供镜像索引以及用户认证的功能,docker registry在远程存放镜像,大致架构如下:



                           +-----------------+
                           | Docker Daemon   |
        ,---.              |  -------------- |                )------------------(
       /     \             |  Container1     |                |                  |
      / Docker\            |                 |--------------- |  Docker Registry |
     (  Client )---------- |  Container2     |                |                  |
      \       /            |                 |                )------------------(
       \     /             |  Container3     |                          |
        `---'              |                 |                          |
     docker pull           |  .......        |                          |
     docker run            |                 |                          |
     docker build          +-----------------+                          |
     docker ...                     |                                   |
                                    |                                   |
                            +----------------+                          |
                            |  Docker Index  |--------------------------+
                            +----------------+

Docker Image

docker image是docker容器的基石,保存了docker启动的各种条件,docker container的启动必须基于一个docker image。Docker image位于docker生命周期的构建和打包阶段。
docker使用了union mount的方式构建镜像,采用了ufs的模型。就是docker一次性加载多个文件系统形成一个文件系统层叠的形式,但是在外部看来容器只有一个文件系统可用,docker将这样的文件系统称为docker image。
docker image是一个层层叠加的只读文件系统。最底部是bootfs,即引导文件系统,当容器启动后,引导文件系统将会被卸载。bootfs之上是rootfs,rootfs可以是ubuntu/centos/debian/fedora,与虚拟机启动后由只读状态变为读写状态不同,docker的rootfs永远是只读。

Docker的UFS示例:
 +---------------+
 |  add django   |
 +---------------+
 |  add python3  |
 +---------------+
 |rootfs(ubuntu) |
 +---------------+
 |     bootfs    |
 +---------------+

Docker Container

Docker Container是Docker的执行单元,通过镜像来启动。Docker container位于docker生命周期的启动和执行阶段。
Docker Container基于镜像启动时,会在镜像构建出的ufs上再添加一个writable layer,一个可写层。当容器刚启动时,该读写层是空的,当容器状态发生改变时,读写层才会有变化。比如当需要修改一个文件时,docker把改文件从下层的只读文件系统中复制到读写层,再进行修改,该文件的只读版本仍然存在,但是已经被读写层上的副本隐藏起来了。这就是Docker中重要的技术Copy on Write(中文翻译为写时复制),每个只读镜像层永远都是只读的,不会变化,当创建出一个新容器时,docker会构建出一个镜像栈,在栈的顶层增加读写层,并与底下的只读层和一些数据就构成了一个容器。容器的分层框架能够使我们快速的构建镜像并发布应用程序。

Docker container创建的镜像栈

 +---------------+
 | add my app    |  读写层(copy on write)
 +---------------+
 |  add django   |  Read Only
 +---------------+
 |  add python3  |  Read Only
 +---------------+
 |rootfs(ubuntu) |  Read Only
 +---------------+
 |     bootfs    |  Read Only
 +---------------+

Docker Registry

Docker仓库分为公有和私有,放着一些已有的镜像,官方的docker registry是Docker Hub

Namespaces

C++中有namespace这个概念,它实现了代码块的封装,更重要的是代码的隔离,使得不同namespace下可以有相同的变量名但意义不同。在linux里,namespace是一个重要的特性,提供了系统资源的隔离,包括进程/网络/文件系统等。linux实现namespace的重要目的就是为了实现轻量级的虚拟化,也就是容器(linux里的lxc了解一下)。在同一namespace下的进程可以感知彼此的变化,而不能发现其他进程,从而使得一个namespace下的进程认为自身处于一个独立的系统中,便达到了独立和隔离的目的。
Docker目前用到了以下几种namespace进行资源隔离。

  • PID, Process ID, 进程隔离;
  • NET, Network, 网络隔离;
  • IPC, InterProcess Communication, 跨进程通信访问隔离;
  • MNT, Mount, 挂载点隔离;
  • UTS, Unix Timesharing System, 内核和版本标识隔离;
  • USER, 用户和组隔离;

而隔离资源的管理就需要用到Control Groups

Control Groups

Control groups是linux提供的限制/记录/隔离主机物理资源的机制,简称cgroups,为了更好的管理容器,cgroups在2007年并入到linux kernel中。没有cgroups,就没有容器。
cgroups主要负责容器相关的以下内容:

  • 资源限制,比如为进程组设置一个内存使用的上限;
  • 优先级设定,比如为某些进程组设定更大的cpu和内存资源。
  • 资源计量,比如计算进程组使用了多少系统资源。
  • 资源控制,比如将进程组挂起和恢复。

正是因为namespaces和cgroups,Docker的容器才拥有了以下能力:

  • 文件系统隔离:每个容器都有自己的rootfs;
  • 进程隔离: 每个容器都运行在自己的进程环境中;
  • 网络隔离: 容器间的虚拟网络接口和IP地址都是分离的;
  • 资源隔离和分组: cgroups将CPU和内存等资源独立分配给每个不同的Docker容器。

容器的状态

  • 运行
  • 已暂停
  • 重新启动
  • 已退出

Command

$ docker help

$ docker help cp
$ docker run --detach --interactive --tty --name web busybox:latest /bin/bash

$ docker run -d -name wp3 --link wpdb:mysql -p 80 -v /run/lock/apache2 -v /run/apache2 --read-only wordpress:4

$ docker run -d --rm --name wp --env MY_ENVIRONMENT_VAR="test" --read-only wordpress:4

$ docker inspect --format "{{.State.Running}}" wp

$ docker ps

$ docker top web

$ docker run --it

$ docker restart web

$ docker logs web

$ docker exec web ps

$ docker stop xxxx

$ docker search postgres

$ docker save -o myfile.tar busybox:latest

$ docker load -i myfile.tar

$ docker rmi busybox

Dockerfile指令

类似于Makefile, Docker可以根据Dockerfile来构建镜像。Dockerfile是一个文本文件,每一行都是一个Dockerfile指令。接着使用docker build命令就可以构建出一个docker镜像了。Dockerfile是自动化运维的关键。

指令格式

注释

# Comment 这是注释

指令

INSTRUCTION arguments

样例

FROM ubuntu:16:04
LABEL author=fwz version=1.0.1
WORKDIR /root/myapp
COPY myapp .
RUN apt update \
    && apt install -y python3 python3-pip
    && pip3 install -r requirement.txt
EXPOSE 8000
ENTRYPOINT ["/usr/bin/python3", "/root/app/manage.py", "runserver"]

FROM

FROM指令指定了一个基础镜像,接下来的指令都是基于这个基础镜像进行构建。每个Dockerfile都必须包含FROM指令。如果FROM指定的基础镜像不在本地,则会从Dockerhub上拉取。

FROM ubuntu:16:04
FROM python:3.7

RUN

RUN指令是构建时执行的指令,。格式是RUN <command>或者RUN ['executable', 'param1', 'param2', ...],RUN指令默认使用shell执行命令,即/bin/sh -c执行命令,如果想使用不同的shell,比如bash,那么可以像这么写:

RUN ['/bin/bash', '-c' 'python3 bootstrap.py']

而且最好RUN指令的内容都写在同一行,防止ufs的层数过多。

CMD

CMD指令是容器启动时执行的指令,可以包含参数,比如:

CMD ["python3", "manage.py", "runserver", "0.0.0.0:8000"]

EXPOSE

用于指定容器监听的内部端口号,不过可以在docker run的时候更改端口映射;

EXPOSE 27017

COPY

把文件从本地复制到容器中

COPY application.yml /app/

ENTRYPOINT

和CMD类似,也是容器启动时执行的命令,不过可以在容器启动即docker run时提供参数

ENTRYPOINT ["java", "app.jar"]

LABEL

记录容器相关的变量,docker inspect时可以看到

LABEL author="菜徐坤" version="7.7.7"

Config Center: Apollo

随着微服务的流行,应用和机器数量急剧增长,程序配置也愈加繁杂:各种功能的开关、参数的配置、服务器的地址等等。同时,我们对程序配置的期望值也越来越高:配置修改后实时生效,灰度发布,分环境、分集群管理,完善的权限、审核机制等等。

一般服务部署到线上之后,当程序发布到生产环境后,一般就是按照预设的逻辑运行,我们无法直接去干预程序的行为,不过可以通过调整配置参数来动态调整程序的行为。

在Spring Cloud的微服务架构方案中虽然提供了Spring Cloud Config来担任配置中心的角色,但是该项目的功能在配置的管理层面还是非常欠缺的。初期我们可以依赖选取的配置存储系统(比如:Gitlab、Github、Gerrit)给我们提供的配置管理界面来操作所有的配置信息,但是这样的管理还是非常粗粒度的。

spring cloud config使用git作分布式配置管理仓库,虽然比较便捷,但是也有一些缺点,比如:修改配置的操作需要修改clone下来的仓库然后再commit和push,缺少通过web界面修改配置的手段;配置需要生效的话,则要求服务重启或者refresh或者git hooks回调等;另外版本管理也是没有比较好的web界面的,只能通过在命令行上使用git命令。授权和审核操作gerrit和gitlab是有提供的,但是粒度比较粗,修改和发布权限没有比较好的分离。在生产环境上,每个服务正在使用什么配置,往往只能靠记忆,很不方便。

spring cloud config既然推荐了git,自然是有优点的,最重要的一点就是足够简单,另外由于缺乏了很多功能,开发人员能开展个性化的开发因而扩展性极强。这就够了。

Ansible

ANSIBLE


[TOC]


场景

为了操作远程主机,我们通常是ssh登录到对应的机子上并输入命令执行,安装软件、修改配置、等等。如果只有单台主机,这么做是完全OK的,顶多是把一些常用操作变成脚本减少重复操作。但是当有了两台主机甚至更多主机之后,同样的命令在另一台主机还要敲一遍,这样的操作就有点枯燥繁琐了。有的人觉得仅仅是多一台主机还好,那如果多10台主机呢,比如需求是在10台机子上配置redis集群,那么ssh登录到每台机子上进行操作是一件很sb的事。

一个简单的解决方案是:使用pssh。pssh可以通过ssh的方式批量发送命令到多台主机并执行,pscp可以批量拷贝文件到多台主机。但是pssh的缺点也很明显,就是功能太少。如果想要通过pssh进行复杂的操作,那就比较困难了。

多主机的配置管理是常见的场景,前人已经造好了轮子,也就是一些自动化运维工具,我们直接使用就行了。比较著名的也是国内用的最多的就是Ansible。

其实本质上Ansible或者说大部分运维工具主要解决以下自动化运维场景:

  • 文件传输
  • 应用部署
  • 配置管理
  • 任务流编排

常用自动化运维工具

自动化运维工具主要可以分为两大类:

  1. 需要装代理的;
  2. 不需要装代理的;

第一种方式是指我们需要预先在目标主机上安装代理软件(在被控端上放个内鬼,就可以交易了),然后主控端通过通知代理软件来操纵被控端。

第二种方式是不需要在被控端安装代理软件,主要是通过ssh连接到被控主机进行控制操作,主控端需要提前得到被控主机的授权(比如秘钥对)才能进行相应的操作。

当前主流的自动化运维工具有:

  • ansible:使用python编写,agentless,适用于中小型应用环境;
  • saltstack: 使用python编写,需要在每台机子上安装agent,执行效率更高;
  • puppet: 使用ruby编写,功能强大,配置复杂,重型,适合大型环境;
  • fabric:使用python编写,agentless,功能比较简单;

由于puppet是使用ruby编写的,国内ruby玩家的数量远少于python,因此saltstack和ansible的使用者相对来说就比较多。fabric由于功能太少,完全能被ansible替代,所以企业用户很少使用fabric做复杂的任务流编排。

Ansible

特性

这里简单说一下ansible的主要特性:

  • 模块化:调用特定的模块,完成特定的任务,比如说rabbitmq模块特定用于部署rabbitmq,redis模块特定用户部署redis等;
  • 有paramiko,pyyaml,jinja2三个关键模块,分别的功能是ssh连接并操作,解析yaml文件,jinjia2模板引擎;
  • 支持自定义模块;
  • 基于python实现;
  • 部署简单,基于python和ssh,agentless;
  • 安全,基于openssh;
  • 支持playbook编排任务;
  • 幂等性,第一次执行某个ansible的操作和第n次执行同样的ansible操作对主机状态的改变结果是一样的;
  • 无需代理不依赖pki;
  • 可使用任何语言写模块;
  • yaml格式,编排任务,支持丰富的数据结构;
  • 较强大的多层解决方案;

安装

只要用pip安装即可。

> pip install ansible

或者

> pip3 install ansible

完成。

配置文件主要在:

  1. /etc/ansible/ansible.cfg,ansible工具本身的配置。
  2. /etc/ansible/hosts,静态主机清单(Inventory)配置。
  3. /etc/ansible/roles/,ansible-roles可以放在这。

组件

Inventory

主机清单。对于批量主机操作,为了便捷的使用其中的部分主机,可以在inventory file中将其分组命名,默认的inventory file为/etc/ansible/hosts,inventory file可以有多个,且也可以通过Dynamic Inventory来动态生成。inventory文件的格式符合INI文件风格,中括号中的字符为组名。可以将同一个主机同时归并到多个不同的组中;如果目标主机使用非默认的SSH端口,还可以在主机名称之后使用冒号加端口号来表明。举个例子:

[webserver]
172.1.1.40:2222
10.2.2.2
www.bbb.com

# 主机机名遵循相似的命名模式,还可使用列表的方式标识个主机
[mysqlserver]
db[01:10].dddd.com

[redisserver]
rds-[a:f].cloud.com

# 在inventory中定义主机时为其添加主机变量以便于在playbook中使用
[appserver]
10.3.3.3 maxMemoryUsage=60
10.4.4.4 servicePort=8099

# 定义指定组内所有主机上的在playbook中可用变量
[appsever:vars]
config-center=172.3.4.5
enable-water=true

AD-Hoc

命令行模式使用ansible。

使用ansible --help可查看如何使用

fwz@fwz-mbp ~/Workspace> ansible --help
Usage: ansible <host-pattern> [options]

Define and run a single task 'playbook' against a set of hosts

Options:
  -a MODULE_ARGS, --args=MODULE_ARGS
                        module arguments
  --ask-vault-pass      ask for vault password
  -B SECONDS, --background=SECONDS
                        run asynchronously, failing after X seconds
                        (default=N/A)
  -C, --check           don't make any changes; instead, try to predict some
                        of the changes that may occur
  -D, --diff            when changing (small) files and templates, show the
                        differences in those files; works great with --check
......
......

简单的说,语法就是ansible <host-pattern> [-f forks] [-m module_name] [-a args]

本质上,就是调用相应的ansible模块,并传入相应的参数。

举几个例子:

ping所有主机(使用ping模块):

> ansible all -a ping --ask-pass
ansible all -m ping --ask-pass
SSH password:
xx.xx.xx.xxx | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

改变文件属性(使用file模块):

> ansible mysql-server -m file -a 'owner=mysql group=mysql mode=644 path=/tmp/fstab.ansible'

拷贝文件(使用copy模块):

> ansible storageIndex-server -m copy -a 'src=/home/devops/cloud-relay-1.2.4-SNAPSHOT.tar.gz dest=/root/cloud-storageIndex/cloud-relay-1.2.4.tar.gz'

配置Rabbitmq(使用rabbitmq_user模块):

> ansible rabbitmq-server -m rabbitmq_user -a 'user=new_user password=root tags=administrator vhost=/ state=present'

执行linux的命令(使用shell模块):

> ansible webserver -m shell -a 'netstat -nutlp > netstat_temp.txt'

Playbook

剧本。虽然可以在命令行中使用ansible,但是明显功能有限,无法支持复杂环境的配置管理工作。首先,命令行一次只能执行一条命令,其次,由于通常是手敲命令,因此不好重复。类似于linux中的shell脚本,ansible提供了playbook,翻译为剧本,用于组织基于多个ansible模块交互的工作流编排。

linux的脚本文件是.sh文件,ansible-playbook则是.yaml文件,即使用YAML语法。YAML的语法和其他高阶语言类似,并且可以简单表达清单、散列表、标量等数据结构。

直接举个例子:

---
# This playbook will install mysql and create db user and give permissions.
- hosts: $servername
  remote_user: root
  - name: Install Mysql package
    yum: name={{ item }} state=installed
    with_items:
     - mysql-server
     - MySQL-python
     - libselinux-python
     - libsemanage-python

  - name: Configure SELinux to start mysql on any port
    seboolean: name=mysql_connect_any state=true persistent=yes
    when: sestatus.rc != 0

  - name: Create Mysql configuration file
    template: src=my.cnf.j2 dest=/etc/my.cnf
    notify:
    - restart mysql

  - name: Start Mysql Service
    service: name=mysqld state=started enabled=yes

  - name: insert iptables rule
    lineinfile: dest=/etc/sysconfig/iptables state=present regexp="{{ mysql_port }}"
                insertafter="^:OUTPUT " line="-A INPUT -p tcp  --dport {{ mysql_port }} -j  ACCEPT"
    notify: restart iptables

  - name: Create Application Database
    mysql_db: name={{ dbname }} state=present

  - name: Create Application DB User
    mysql_user: name={{ dbuser }} password={{ upassword }} priv=*.*:ALL host='%' state=present

例子很好理解,首先是定义了这个playbook在哪个主机组中执行,以及对应的用户,接着是一组task,分别调用了yum、seboolean、template、service、lineinfile、mysql_db、mysql_user模块。其中使用了notify作handler。

hosts:playbook中的每一个play的目的都是为了让某个或某些主机以某个指定的用户身份执行任务。hosts用于指定要执行指定任务的主机,其可以使一个或多个由冒号分隔主机组;

remote_user: remote_user也可用于各task中,也可以通过指定其通过sudo的方式在远程主机上执行任务,其可用于play全局或其任务;此外,甚至可以在sudo时使用sudo_user指定sudo时切换的用户。

task:play的主题部分是task list。task list中的各任务按次序逐个在hosts中指定的所有主机上执行,即在所有主机上完成第一个任务后再开始第二个。在运行自上而下某playbook时,如果中途发生错误,所有已执行任务都可能回滚,在更正playbook后重新执行一次即可。

taks的目的是使用指定的参数执行模块,而在模块参数中可以使用变量。模块执行是幂等的。这意味着多次执行是安全的,因为其结果均一致。

每个task都应该有其name,用于playbook的执行结果输出,建议其内容尽可能清晰地描述任务执行步骤,如果为提供name,则action的结果将用于输出。

定义task可以使用”action: module options”或”module:options“的格式推荐使用后者以实现向后兼容。如果action一行的内容过多,也中使用在行首使用几个空白字符进行换行。

tasks:
 - name:make sure apache is running
   service: name=httpd state=started

在众多的模块中,只有command和shell模块仅需要给定一个列表而无需使用”key=value”格式

tasks:
 - name: run this command and ignore the result
   shell: /usr/bin/somecommand || /bin/true

Handler: 用于当关注的资源发生变化时采取一定的操作。

“notify”这个action可用于在每个play的最后被触发,这样可以避免多次有改变发生时每次都执行执行的操作,取而代之,仅在所有的变化发生完成后一次性地执行指定操作,在notify中列出的操作称为handlers,也即notify中调用handlers中定义的操作。

- name: template configuration file
 template: src=template.j2 dest=/etc/foo.conf
 notify:
   - restart memcached
   - restart apache

Roles

ansible自1.2版本引入的新特性,用于层次性、结构化地组织playbook。roles能够根据层次型结构自动转载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单来讲,roles就是通过分别将变量、文件、任务、模板以及处理器放置于单独的目录中,并可以便捷地include他们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以使用于构建守护进程的场景中。

一个常用的role结构:

site.yml
webserver.yml
fooserver.yml
roles/
   common/
       files/
       templates/
       tasks/
       handlers/
       vars/
       meta/
   webserver/
       files/
       templates/
       tasks/
       handlers/
       vars/
       meta/

在ansible-playbook中可以这么使用role:

- hosts: webserver
 roles:
   - common  
   - webserver

向role中传递参数:

- hosts: webserver
 roles:
   - common
   - { role: foo_app_instance, dir:'/opt/a',port:5000}
   - { role: foo_app_instance, dir:'/opt/b',port:5001}
  • task目录:至少应该包含一个为main.yml的文件,其定义了此角色的任务列表;此文件可以使用include包含其它的位于此目录中的task文件;
  • file目录:存放由copy或script等模板块调用的文件;
  • template目录:template模块会自动在此目录中寻找jinja2模板文件;
  • handlers目录:此目录中应当包含一个main.yml文件,用于定义此角色用到的各handlers,在handler中使用inclnude包含的其它的handlers文件也应该位于此目录中;
  • vars目录:应当包含一个main.yml文件,用于定义此角色用到的变量
  • meta目录:应当包含一个main.yml文件,用于定义此角色的特殊设定及其依赖关系;ansible1.3及其以后的版本才支持;
  • default目录:应当包含一个main.yml文件,用于为当前角色设定默认变量时使用此目录;

官方提供了很多ansible-roles的例子:ansible/ansible-examples

Galaxy

用于分享和下载ansible-role的平台,类似于同性交友网站github。

Points on Serverless

关于Serverless架构

Abstract

最近写的项目其中一个功能点用到了阿里云的函数计算(function computing, 简称fc)。fc属于serverless computing架构模式,是一种新兴的云计算架构,在云计算中也被描述为Faas(Function as a Service),在2014年Amzon首先推出了AWS Lambda,之后各大主流云厂商跟进,在2016年也分别推出了自家的serverless服务,比如阿里的fc和腾讯的scf等。

阿里云函数计算是事件驱动的全托管计算服务。通过函数计算,您无需管理服务器等基础设施,只需编写代码并上传。函数计算会为您准备好计算资源,以弹性、可靠的方式运行您的代码,并提供日志查询、性能监控、报警等功能。借助于函数计算,您可以快速构建任何类型的应用和服务,无需管理和运维。而且,您只需要为代码实际运行所消耗的资源付费,代码未运行则不产生费用。

什么是serverless computing

概念

serverless computing是指开发者在构建和运行应用时无需管理服务器等基础设施。应用被解耦为细粒度的函数,函数是部署和运行的基本单位。用户只为实际使用的资源付费。serverless computing能够帮助应用开发者摆脱服务器等底层基础设施管理的负担,专注于业务层的创新。Serverless不是真的不需要服务器了,而是不用过多的关注服务器。在微服务的架构下,系统被拆分为一系列独立的服务,serverless的粒度更小,更组件化。

serverless函数是基于容器运行的,每个函数实例的运行时环境都由容器提供;每一次函数被调用都会serverless里的调度器都会首先首先查找可用容器,没有可用容器则会创建一个容器并拉取代码来运行函数,这就是冷启动,冷启动比较耗时。

发展

计算的发展演进:

物理机 --> 虚拟机 --> 云计算 --> 容器 --> serverless

云计算:

-------------
|   FaaS    |
-------------
|   Saas    |
-------------
|   Pass    |
-------------
|   IaaS    |
-------------

从大型物理机到通过虚拟化技术把物理机虚拟成单个的VM资源,从虚拟化集群到把集群搬到云计算上只做简单运维,再到把每一个VM按照运行空间最小化切分成更细的Docker容器,再从Doceker容器变成直接不用管理任何运行环境的Serverless服务。

优势

  1. 节约使用成本; 虚拟机通常并不能满负载运行,有一些资源浪费。
  2. 简化设备运维; 无需维护基础设施;
  3. 实现快速上线; 部署只需要几步操作;
  4. 降低开发成本; 只需要关注业务代码(java除外)和相关组件;
  5. 自动扩展能力;

劣势

  1. 缺乏调试和开发工具;
  2. 不适合长时间运行应用; serverless函数都有最长运行时间限制;
  3. 完全依赖云厂商;
  4. 冷启动时间; serverless函数第一次启动非常耗时;

Reference

serverless-computing-wikipedia

serverless.com

Serverless 架构应用开发指南

函数计算-阿里云

Thinking in Function Programming

Thinking in Function Programming

Functor

TODO: explain it

No Side Effect

TODO: explain it

Currying

TODO: explain it

Lazy Evaluation

TODO: explain it

Closure

TODO: explain it

Implements

In Python

Python并不是函数式编程语言, 只是支持一些函数式编程的特性

In Java

Java在8之前不支持函数式编程。Java8新增Stream API支持以一种声明的方式处理数据;
例如:

// java11
var list = List.of("a", "b", "c", "1");
var listUpper = list.parallelStream()
                    .filter(s -> !s.isEmpty())
                    .map(s -> s.toUpperCase())
                    .collect(Collectors.toList());
var sortedList = list.stream()
                    .sorted()
                    .collect(Collectors.joining(","));

In JS

Reference

  1. Why Functional Programming Matters;