跳转到主要内容

Hanny的传奇基础设施即代码解决方案。

项目描述

Infrable

Hanny的传奇基础设施即代码解决方案。

PyPI version

# Install
pip install -U infrable  # requires python >= 3.10

# Bootstrap a new project
infrable init

目录

  1. 序言
  2. 第一章 - 混沌
  3. 第二章 - 主机和服务
  4. 第三章 - 模板
  5. 第四章 - 部署或恢复工作流
  6. 第五章 - 命令、任务和工作流
  7. 第六章 - 环境和开关
  8. 第七章 - 元数据和秘密
  9. 第八章 - 自定义模块

序言

在一个由闪烁的灯光包围的无限虚空中,Hanny醒来时记忆空洞,只有她的名字在她的脑海中回响。她发现自己漂浮在一个介于有形世界的Python代码和虚幻宇宙之间的领域。一个合成声音在她周围回荡,提到了一个关键的基础设施迁移项目,但她对此一无所知。她的记忆,曾经是Python王国的钥匙,无法提供任何答案。她被困在一个星际迷宫中,承担着一个她无法回忆的技术使命,她唯一的是机械键盘和附近屏幕上飞逝的代码行。

第一章 - 混沌

在她的不满的空虚中,Hanny开始回忆起前世的片段:在地球上的过去,一个迫在眉睫的基础设施迁移项目威胁着一切。人类在与混乱的基础设施和准备不足的工具包作斗争。Python开发者与复杂的工具作斗争,而绝望地寻找声明性配置管理也徒劳无功。在这片混乱之中,Hanny被抛入宇宙,一个不愿意的骑士,被赋予了一个令人难以置信的任务:找到一个简单但能够驾驭这场即将到来的灾难的解决方案。

第二章 - 主机和服务

在顿悟的一刹那,Hanny理解了基础设施的复杂性:主机和它们所承载的服务,这是任何系统的关键骨干。她认识到维护一个单一参考来源,一个完整、和谐的文档,捕捉这些基本关系的重要性。她致力于维护这个单一真理来源的原则,开始艰苦的文档工作,将每一个细节都倾注到名为"infra.py"的整合证明中。

infra.py

from infrable import Host, Service

# Hosts/ -----------------------------------------------------------------------
dev_host = Host(fqdn="dev.example.com", ip="127.0.0.1")
beta_host = Host(fqdn="beta.example.com", ip="127.0.0.1")
prod_host = Host(fqdn="prod.example.com", ip="127.0.0.1")
# /Hosts -----------------------------------------------------------------------


# Services/ --------------------------------------------------------------------
dev_web = Service(host=dev_host, port=8080)
beta_web = Service(host=beta_host, port=8080)
prod_web = Service(host=prod_host, port=8080)

dev_nginx = Service(host=dev_host, port=80)
beta_nginx = Service(host=beta_host, port=80)
prod_nginx = Service(host=prod_host, port=80)
# /Services --------------------------------------------------------------------

列出主机和服务

infrable hosts
infrable services

第三章 - 模板

逐渐拼凑起零散的记忆,Hanny意识到主机部署的配置文件应该作为模板来维护,从"infra.py"中按需提取值。回到地球后,挑战是缺乏一个连贯的系统来记录这些文件的目标位置,这是一个组织问题,构成了一个重大的障碍。然后,在一个天才的时刻,Hanny提出了一个突破性的解决方案。她将在配置模板中直接记录文件的路径。于是,她以新的活力,开始在配置文件中添加重要的细节,作为头注释,这是一个传奇性的举动,有望改变基础设施迁移的面貌。

infra.py

template_prefix = "https://github.com/username/repository/blob/main"

templates/nginx/web.j2

# vim: syn=nginx

# ---
# src: {{ template_prefix }}/{{ _template.src }}
# dest: {{ dev_nginx.host }}:/etc/nginx/sites-enabled/web
# chmod: 644
# chown: root:root
# ---

server {
    listen {{ dev_nginx.port }};
    listen [::]:{{ dev_nginx.port }}

    server_name {{ dev_nginx.host.fqdn }} www.{{ dev_nginx.host.fqdn }};

    location / {
        proxy_pass http://127.0.0.1:{{ dev_web.port }};
        include proxy_params;
    }
}

注意:_template.src是一个特殊变量,在所有模板中可用。

第四章 - 部署或恢复工作流

在充满不确定性的广阔天地中,Hanny清楚地意识到:在推送之前,审查通过"infra.py"生成的文件并与当前部署的配置进行比较的重要性。这个过程将拦截任何实时更改,并确保它们包含在模板中。她的谨慎性格也认识到维护所使用配置的本地备份的必要,这提供了一种保险。为了解决任何复杂问题,她构想了一个安全措施:部署或恢复工作流程。这承诺缓解人为错误,同时确保可以轻松地回滚和恢复服务,这是Hanny在宇宙故事中的又一成功步骤。

部署工作流程

infrable files deploy [path]

## Same as
# infrable files gen [path]
# infrable files pull
# infrable files backup
# infrable files push

为蛇人

from infrable import files

files.deploy(path)

## Same as
# files.gen(path)
# files.pull()
# files.backup()
# files.push()
flowchart TD;
    A[gen: generate artifacts from templates and infra.py as .new files] --> B[pull: for each generated artifact pull the currently deployed version from server as .old files];
    B --> C[backup: copy the artifacts in a local backup directory for easy recovery in case of failure];
    C --> E[diff: compare the .new and .old files];
    E -- push: for each result --> F{is there any difference?};
    F -- yes --> H[display the diff];
    F -- no --> G[skip and delete artifacts];
    H --> I{confirm push?};
    I -- yes --> J[push the file onto the server];
    I -- no --> K[skip and delete artifacts];

差异示例

--- .infrable/files/root@dev.example.com/etc/monit/conf.d/system.cfg.old

+++ .infrable/files/root@dev.example.com/etc/monit/conf.d/system.cfg.new

@@ -1,13 +1,14 @@

 check system dev.example.com
-    if memory usage > 85% then alert
+    # if memory usage > 85% then alert
     if cpu usage (user) > 80% for 3 cycles then alert
     if cpu usage (system) > 80% for 3 cycles then alert

Push? (y, n, all) [y]:

恢复工作流程

infrable files recover [path]

## Same as
# infrable files revert [path]
# infrable files push

为蛇人

from infrable import files

files.recover(path)

## Same as
# files.revert(path)
# files.push()
flowchart TD;

    A[revert: copy the artifacts from the given or latest backup directory into artifacts directory but in reverse order] --> B[diff: compare the .new and .old files];
    B --> C[push: run the steps for the push workflow]

第五章 - 命令、任务和工作流

随着Hanny深入了解基础设施迁移的复杂性,她遇到了一个关键的认识:需要超越仅仅推送配置。为了实现无缝过渡,测试、服务重启和其他部署后的操作是必不可少的。她以不懈的决心,在"infra.py"中集成了一个功能,以在主机上执行远程命令,这是一个增强部署过程的能力。

# Run a command on a host by name
infrable remote dev_host "sudo systemctl reload nginx"

# Or by service name
infrable remote dev_nginx "sudo systemctl reload nginx"

# Or all affected hosts (as per files diff)
infrable remote affected-hosts "sudo systemctl reload nginx"

# Or
infrable files affected-hosts | infrable remote - "sudo systemctl reload nginx"

Hanny对卓越的追求并未停止;她认识到松散命令执行的不充分,承认结构化组织的必要性。因此,她构想了一个新概念——创建“任务”,这些是设计用于简化操作的命令组。

infra.py

import typer

# Tasks/ -----------------------------------------------------------------------
dev_nginx.typer = typer.Typer(help="dev_nginx specific tasks.")

@dev_nginx.typer.command(name="reload")
def reload_dev_nginx():
    """[TASK] Reload nginx: infrable dev-nginx reload"""

    assert dev_nginx.host, "Service must have a host to reload"
    dev_nginx.host.remote().sudo.nginx("-t")
    dev_nginx.host.remote().sudo.systemctl.reload.nginx()
# /Tasks -----------------------------------------------------------------------

运行任务

infrable dev-nginx reload

在她为实现这一愿景而努力的过程中,一个绝妙的想法闪过她的脑海——将这些任务编排成连贯的序列,她称之为“工作流程”,这标志着基础设施管理新时代的到来。

infra.py

from infrable import concurrent, paths

# Workflows/ -----------------------------------------------------------------------
deploy = typer.Typer(help="Deployment workflows.")

@deploy.command(name="dev-nginx")
def deploy_dev_nginx():
    """[WORKFLOW] Deploy dev_nginx files: infrable deploy dev-nginx"""

    files.deploy(paths.templates / "nginx")
    cmd = "sudo nginx -t && sudo systemctl reload nginx && echo success || echo failed"
    fn = lambda host: (host, host.remote().sudo(cmd))
    for host, result in concurrent(fn, files.affected_hosts()):
        print(f"{host}: {result}")
# /Workflows -----------------------------------------------------------------------

运行工作流程

infrable deploy dev-nginx

第六章 - 环境和开关

进步的喜悦迅速被为多个环境中的每个主机独立定义模板、任务和工作流程的艰巨性所抵消。汉妮敏锐的眼睛疲惫地眨着,手指因不停地敲击而疼痛。就在那时,她突然意识到另一个重要的现实:服务与主机的关系取决于环境。环境——“开发”、“测试”、“生产”等——决定了服务的部署位置。她思考着是否可以切换环境,提出了一个关键问题:如果“infra.py”文件能够根据环境调整值会怎样?这将极大地简化部署过程,利用相同的模板、任务和工作流程定义在不同主机上。抓住这个灵感,汉妮开发了‘Switch’,这是一种巧妙的机制,允许在“infra.py”中进行环境相关的值调整。有了‘Switch’,基础设施迁移不再是迷宫般的导航,而是一次探索和创新之旅。随着她每编写出一行代码,任务的重量都在减轻。于是,汉妮在宇宙的寂静中继续前行,她的精神不可战胜,她的热情难以驯服。

infra.py

from infrable import Switch, Host, Service

# Environments/ ----------------------------------------------------------------
dev = "dev"
beta = "beta"
prod = "prod"

environments = {dev, beta, prod}
env = Switch(environments, init=dev)  # <-- Defining a switch for different environments
current_env = env()
# /Environments ----------------------------------------------------------------

# Hosts/ -----------------------------------------------------------------------
dev_host = Host(fqdn="dev.example.com", ip="127.0.0.1")
beta_host = Host(fqdn="beta.example.com", ip="127.0.0.2")
prod_host = Host(fqdn="prod.example.com", ip="127.0.0.3")

managed_hosts = env(  # <-- Switching hosts based on the environment
    dev=[dev_host],
    beta=[beta_host],
    prod=[prod_host],
)
# /Hosts -----------------------------------------------------------------------

# Services/ --------------------------------------------------------------------
web = Service(
    host=env.strict(dev=dev_host, beta=beta_host, prod=prod_host),  # <-- Strict switch
    port=8080,
)

nginx = Service(port=80, host=web.host)  # <-- No need to use switch here
# /Services --------------------------------------------------------------------

更新模板以使用可切换的值

templates/nginx/proxy_params.j2

# vim: syn=nginx

# ---
# src: {{ template_prefix }}/{{ _template.src }}
# dest:
# {% for host in managed_hosts %}  # <-- Yes, you can
#   - loc: {{ host }}:/etc/nginx/proxy_params
# {% endfor %}
# chmod: 644
# chown: root:root
# ---
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

管理开关

# Switch current environment
infrable switch env [dev|beta|prod]

# Check the current switch value
infrable switch env

# See other options for the switch
infrable switch env --options

# If you name a switch "env", you get an alias for convenience
infrable env [dev|beta|prod]

# Check all switch values
infrable switches

第七章 - 元数据和秘密

神秘的面纱笼罩着汉妮最新的任务。她渴望能够访问那些未被版本控制系统眼睛触及的机密值,并将这些值与主机和服务进行对比。这是一个艰巨的挑战——在不增加复杂性的情况下,将这些机密密钥嵌入Python迷宫中。她沉思着,目光聚焦于宇宙中散落的星星。如果简单是发明的母亲,汉妮将是其虔诚的弟子。她遵循着伟大的Python传统——简单,凭借从前挑战中获得的智慧,投入到眼前的任务中。她制定了一个优雅的策略。她的解决方案是诗人的梦想,是程序员的喜悦,她成就的欣慰在宇宙的寂静中回荡。

infra.py

from infrable import Meta, readfile

common_secret_key = readfile("secrets/common/secret_key")  # <-- Read a secret file

web = Service(
    meta=Meta(secret_key=common_secret_key),  # <-- Attach metadata to items
    host=env(dev=dev_host, beta=beta_host, prod=prod_host),
    port=8080,
)

管理机密

# Hide secrets from git
echo /secrets/ >> .gitignore

# Update the secrets by hand
vim secrets/common/secret_key

第八章 - 自定义模块

当天体的寂静包围着她时,汉妮盯着最后一行代码,终于松了一口气。Python大师克服了重重困难,在基础设施迁移的广阔星系中开辟了一条道路。在她的手中,人类掌握了对抗不可避免的基础设施迁移的幽灵的武器,不仅一次,而且可能是无数次。他们不再会在迁移面前挣扎,因为‘infrable’已经到来。

但宇宙的无限空间若能证明什么,那就是无限的成长潜力。汉妮始终是一个有远见的人,她看到了创建工具箱的艰辛背后。她设想了一个不断进化的机制,一个由全球Python同行集体智慧增强的协作系统。就在这时,她突然意识到Python中蕴含的适应性。其他程序员可以创建Python模块并将它们拼接到现有的“infra.py”中,从而扩展和提高其功能,技能和代码的协同作用将“infrable”提升到未知的领域。

modules/mycloud.py

from dataclasses import dataclass
from typer import Typer
from infrable import Host, infra

@dataclass
class MyCloud:
    """MyCloud Python library."""

    secret_api_key: str
    typer: Typer | None = None

    def provision_ubuntu_host(self, fqdn: str):
        ip = self.api.create_ubuntu_host(fqdn)
        return MyCloudUbuntuHost(fqdn=fqdn, ip=ip)

@dataclass
class MyCloudUbuntuHost(Host):
    """MyCloud's customized Ubuntu server."""

    def setup(self):
        self.install_mycloud_agent()

    def install_mycloud_agent(self):
        raise NotImplementedError

workflows = Typer()

@workflows.command()
def provision_ubuntu_host(fqdn: str, setup: bool = True):
    """[WORKFLOW] Provision Ubuntu host."""

    # Get the MyCloud instance from infra.py
    cloud = next(iter(infra.item_types[MyCloud].values()))

    # Provision the host
    host = cloud.provision_ubuntu_host(fqdn)
    if setup:
        host.setup()

    name = fqdn.split(".")[0].replace("-", "_")
    print("Add the host to the infra.py file.")
    print(f"{name} = {repr(host}")

在infra.py中插入模块

infra.py

from modules import mycloud

# Clouds/ ----------------------------------------------------------------------
cloud = mycloud.MyCloud(secret_api_key=readfile("secrets/mycloud/secret_api_key"))
cloud.typer = mycloud.workflows
# /Clouds ----------------------------------------------------------------------

运行模块工作流程

infra cloud --help

项目详情


下载文件

下载适合您平台文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分布

infrable-0.1.40.tar.gz (35.9 kB 查看哈希值)

上传时间:

构建分发版

infrable-0.1.40-py3-none-any.whl (31.6 kB 查看哈希值)

上传时间: Python 3

由以下支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页