Awx

Awx

Awx

作为运维人,对于ansible一定非常熟悉,也可能编写一些自己称手的roles等。 然而你是否有如下的问题:

  • roles是通过scm管理的吗
  • ansible一次执行就结束了吗,别人怎么使用
  • ansible任务能够定时和API触发吗
  • ansible任务谁都可以触发吗
  • ansible任务的结果反馈了吗 如何跟踪错误成功
  • ansible资产是怎么管理的?动态资产还是静态资产?
  • 你的机密信息是否直接在代码里面?是否希望在分发的时候隐藏?
  • 是否在进行一些重复的安装或者性能测试工作?而每次只是ip不同?

这个时候我想说,你需要了解下awx(ansible tower的开源版本)。

awx是一个ansible的管理平台,通过receptoransible-runner 来下发自己的命令并且记录。web组件由django rest framework开发。

相关文档:

安装

由于没有提供standalone的安装方式,其实awx的安装并不会那么方便。这里我 只建议用awx-operator的方式安装。那么你首先需要安装minikube or k8s。这 里我忽略这部分的安装。

这里我简单介绍下阿里云kubernetes下的awx的安装。

helm安装和配置源

1
2
3
4
wget https://get.helm.sh/helm-v3.11.1-linux-amd64.tar.gz
tar xf helm-v3.11.1-linux-amd64.tar.gz
cp linux-amd64/helm /usr//local/bin/
chmod u+x /usr/local/bin/helm

添加源和下载awx-operator helm的源码,后续需要修改

1
2
helm repo add awx-operator https://ansible.github.io/awx-operator/
wget https://github.com/ansible/awx-operator/releases/download/2.2.1/awx-operator-2.2.1.tgz

安装awx chart

首先修改kube-rbac-proxy的源,默认是gcr.io,,阿里云无法pull,直接默认 helm install会报错,镜像无法拉起,修改 awx-operator/templates/deployment-awx-operator-controller-manager.yaml 里面的image替换成quay.io/brancz/kube-rbac-proxy:v0.13.0,你也可以替换 当时的版本

1
2
3
[xxx awx-operator]# kubectl get pods -n awx
NAME                                               READY   STATUS    RESTARTS   AGE
awx-operator-controller-manager-123-tbhxc   2/2     Running   0          26h

注意./awx-operator对应你的github解压的目录,安装命令大概如下,注意先别 执行。

1
 helm install ansible-awx-operator ./awx-operator -n awx --create-namespace

如果只是这样,k8s只会安装awx-operator-controoler, 需要云盘购买50Gi的盘,kubernetes控制台切到对应的namespace然后新建存储卷添加绑定,然后进行下一步

添加具体的应用的yaml请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[root@xxx awx-operator]# cat demo.yaml 
---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx-wooduan
spec:
  service_type: nodeport
  postgres_storage_class: disk
  postgres_storage_requirements:  
    requests:
      storage: 50Gi

阿里云注意类型选择disk如果用的disk类型,默认大小是8Gi,我暂时没解决

1
kubectl apply -f demo.yaml

这样就可以拉起postgresql和其他应用了,注意检查pvc的情况,一般是自动绑 定到新建的存储了。

1
2
3
4
5
6
[root@xxx awx-operator]# kubectl get pods -n awx
NAME                                               READY   STATUS    RESTARTS   AGE
awx-operator-controller-manager-54797df444-tbhxc   2/2     Running   0          26h
awx-wooduan-postgres-13-0                          1/1     Running   0          9m36s
awx-wooduan-task-569fcc89b9-dq2xp                  4/4     Running   0          8m37s
awx-wooduan-web-79868d57cf-tsfgv                   3/3     Running   0          7m11s

剩下的就是找到对应服务,访问方式,放到nginx的后面

和获取admin密码

1
2
3
4
[root@xxx awx-operator]# kubectl get secrets -n awx | grep -i admin-password
awx-xxx-admin-password                    Opaque                                1      11m
[root@xxx awx-operator]# kubectl get secret awx-xxx-admin-password -o jsonpath="{.data.password}" -n awx | base64 --decode ; echo
xxx

但是可能认证失败,看返回提示是csrf报错,这里需要在demo.yaml

最终解决办法是demo.yaml增加如下配置

1
2
3
4
  extra_settings:
  - setting: CSRF_TRUSTED_ORIGINS
    value:
      - http://your_domain

然后

1
2
kubectl delete -f demo.yaml
kubeclt create -f demo.yaml

使用

使用前理一下思路

组件和组件关系

我们来看ansible的一次运行,分别是ansible ad-hoc方式和ansible-playbook 剧本方式。

1
2
ansible -i INVENTORY -m shell -a "echo Hello, world!"
ansible-playbook -i INVENOTRY.py hello-word.yml

发现由如下组件组成:

  • 执行器EE(ansible excute enviroment)

在上面是ansible这个命令,其实这个也隐藏了ansible调用的roles和 collections。 一个执行器环境对应awx里面的执行环境。这个执行环境通常是一个容器, 比如quay.io/ansible/awx-ee:latest,也能是一个网络的 quay.io/ansible/network-ee, 或者等等,你也可以pull他们的镜像进行自己的 组装。

这里我们以https://github.com/ansible/awx-ee awx-ee来简单说说。 首先一个ee类的容器通过ansible-builder build出来的。可以按照里面的说明 自己build一下,方便理解ansible ee。在这里你可以放你的ansible.cfg,放你 的默认roles,放你的ansible collections,如果你是一个网络工程师,那你可 以基于network-ee放你想要的plugins等。

你会发现一个execution node是由系统是centos stream 9,包含ansible-runner 和receptor等软件来组成的。recetpor负责和controller通信,ansible-runner 负责实际的运行。

  • 资产

\-i后面跟着的就是资产,ansible awx如何做资产管理?和我们CMDB类似,通过 一定的层级关系。 按照如下的关系 机构-清单-组-主机

a)机构表示最大的组织orgnization,比如某某公司

b)清单表示包含各个组各个主机的资产清单

c)组和ansible里面的分组一致

d)主机即ansible里面最小的执行单元

  • 凭证

登陆机器,通常需要密码,或者私钥,这个时候你需要保存在凭证的地方。

  • 模板

无论是一次执行Ad-Hoc方式还是Playbook剧本方式,都需要定义你要执行的内容。

这个在awx里面是通过定义模板的方式执行。在模板里面你可以定义是否需要选 择,定义你使用的资产,凭证,还有各种参数等等。

从问题出发介绍使用

最后带着这几个问题去了解ansible awx

  • 支持LDAP登录
  • 支持源码仓库管理
  • 支持动态资产
  • 能运行hello world
  • 机密信息安全加密
  • 能通过动态触发
LDAP认证

设置-LDAP里面进行设置即可,尤其注意LDAP 用户 DN 模板开始是cn=还是啥。

在测试登陆的时候可以通过kubectl logs查看对应的日志

1
kubectl logs -f awx-wooduan-web-xxx awx-wooduan-web
创建机构,部门,团队等等

新建机构 ../images/awx/new-org.png◎ ../images/awx/new-org.png

新建团队 ../images/awx/new-team.png◎ ../images/awx/new-team.png

支持源码仓库管理

由于下一步的新建项目一般是从gitlab等源里面导入的,所以我们需要新建一个 源凭证。 ../images/awx/add-source-credential.png◎ ../images/awx/add-source-credential.png

新建源码项目

由于awx默认会找playbook yaml里面找roles,所以我个人建议的项目结构可以 这样,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
➜  awx-example git:(main) ✗ tree
├── binoo-project
│   ├── hello-world -> ../roles/hello-world
│   ├── hello-world.yml
│   └── oonib -> ../roles/oonib
├── README.md
├── roles
│   ├── hello-world
│   │   ├── defaults
│   │   │   └── main.yml
│   │   └── tasks
│   │       └── main.yml
│   └── oonib
│       └── tasks
│           └── main.yml
└── scripts
    └── dynamic-inventory.py

11 directories, 6 files

个人也是比较建议通过review工具进行review,比如gerrit

项目目录结构大概建议如下:

  • awx项目单独建一个目录,playbook xxx.yml可以迁移到awx-project/下面
  • roles通过软链的方法进行链接,如果是项目相关的,则不用软链
  • 动态资产可以放到scripts目录中
新建项目

awx里面的项目可以理解为你的ansible工程,每个项目对应一个源码仓库。 ../images/awx/add-project.png◎ ../images/awx/add-project.png

这个时候我们就新建好一个项目啦,后面我们的操作都是基于项目操作的。

这里需要注意: 建议勾选启动时更新修订 即每次启动关联这个项目的job都会先从gitlab或者 github进行拉取最新的资源。

另外建议缓存设置一定的超时时间,避免重复拉取~

新建完成以后就会自动去同步项目啦, ../images/awx/project-new-after.png◎ ../images/awx/project-new-after.png

你也可以去左边的视图栏的作业里面查看类型为源控制更新的job,在awx里面 job的含义就是一次运行,比如你在ansible服务器上执行的

1
ansible-playbook -i test hello-world.yml

执行完一次就是这次的job,每个job是由资源里面的模板生成。

确认在job里面找到project同步成功以后就可以继续进行后面的动作了。 ../images/awx/project-sync-successfully.png◎ ../images/awx/project-sync-successfully.png

支持动态资产

何为清单?刚开始我也是很迷糊,其实这里你可以理解是去吃饭的时候对应的小 票或者清单,清单的含义就是包含多个资产多个组的列表,对应ansible里面的 执行对象。

左边选择资源下面的清单,添加清单 ../images/awx/add-manifest.png◎ ../images/awx/add-manifest.png

动态资产

创建完清单点击清单进入详情页面,选择源,添加 ../images/awx/manifest-source.png◎ ../images/awx/manifest-source.png

添加源,这里选择到我们自己的测试python脚本

 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
  #!/usr/bin/env python3
  # -*- coding: utf-8 -*-
  # Date: 2023-07-19T15:10:49+08:00
  # Author: liuliancao <liuliancao@gmail.com>
  """Description: Testing dynamic inventory py for awx."""

  import argparse
  import json

  class AwxInventory:
      """Awx inventory test."""
      def get_hosts(self):
	  hosts = {
	      "_meta": {"hostvars": {}},
	      "group-1": {
		  "hosts": ["172.31.253.73"],
		  "vars": {
		      "group_name": "group-1"
		  }
	      },
	      "group-2": {
		  "hosts": ["172.31.253.75"],
		  "vars": {
		      "group_name": "group-2"
		  }
	      }
	  }
	  return json.dumps(hosts)

      def get_host(self, host):
	  return []


  if __name__ == '__main__':
      arg_parser = argparse.ArgumentParser(description='get awx dynamic inventory')

      mandatory_options = arg_parser.add_mutually_exclusive_group()

      mandatory_options.add_argument('--list',
				     action='store_true',
				     help="show group servers")

      mandatory_options.add_argument('--host',
				     help="show specific server info")

      try:
	  ai = AwxInventory()
	  args = arg_parser.parse_args()
	  if args.host:
	      print(ai.get_host(host=args.host))
	  elif args.list:
	      print(ai.get_hosts())
	  else:
	      raise ValueError("Expecting either --host $HOSTNAME or --list")
      except ValueError:
	  raise

注意脚本需要增加可执行权限,测试的时候可以用如下命令测试

1
ansible-inventory -i dynamic-inventory.py --list

../images/awx/add-manifest-from-source.png◎ ../images/awx/add-manifest-from-source.png

这个时候进入清单里面的源选项,进行同步,如下是同步好对应的的job输出 ../images/awx/inventory-synced.png◎ ../images/awx/inventory-synced.png

这个时候之前动态的资产就导入了 ../images/awx/manifests-inventory.png◎ ../images/awx/manifests-inventory.png

这里关于资产你也可以导入类似aws等云平台的资产,你可以参考更多的关于awx的资产添加 文档。

个人觉得动态资产比较方便导入cmdb等信息,手动维护失去了自动化的味道。 当然你也可以手动维护在你的源码仓库里面,自动更新仓库。

新建机器凭证

机器导入了,再对他们执行任务之前,我们还需要配置用于登陆的私钥或者密码。

../images/awx/add-machine-crendential.png◎ ../images/awx/add-machine-crendential.png

终于配置好了机器私钥,离hello world又近了一步。

能运行hello world
创建hello world模板

点击模板,新建模板 ../images/awx/add-job-template.png◎ ../images/awx/add-job-template.png

需要注意

  • 选择刚刚创建的 清单 ,包含两个机器,
  • 选择对应的 项目
  • 选择对应的 机器凭证
  • 执行环境刚开始建议不选,否则你可能会提示没有容量

这里有启动提示的选项,这个就是你点击运行的时候,是否让你选,如无必要可 以不选。

最终创建好了以后,如下界面 ../images/awx/job-template-created.png◎ ../images/awx/job-template-created.png 可以点击启动了。

刚开始别急,会先同步项目文件,同步资产,具体你可以在左侧作业选项里面看到。

机密信息安全加密
playbook的yaml加密
1
ansible-vault encrypt your_playbook.yml

总结下步骤

  • ansible-vault encrypt 加密你的文件以后,会获得一个密码
  • 在awx里面新建一个type为Vault的类型的credential
  • 在你新建的job模板里面的credential里面添加你新建的vault类型的 credential
  • 再次尝试运行

这样直接加密会影响可读性,其实我们只要把密码抽离到vars就好了。

ssh密码从hashicorp vault获取

目前我能找到用的到的地方是当你再次创建凭证的时候, 用户名和密码等可以点击选择外部的hashicorp vault ../images/awx/out-vault.png◎ ../images/awx/out-vault.png

playbook里面引用lookup from vault or hashicorp vault

有的时候我们可能想在playbook里面引用hashicorp的vault信息

hashicorp这个目前还在了解中,可能不支持。

ansible vault其实可以通过加密vars.yml或者加密defaults/main.yml等方式来 控制。

playbook放置密码等信息

通过var_files参数控制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
- name: testing vault message
  hosts: all
  gather_facts: no
  vars_files:
    - vault-test-vars.yml

  tasks:
    - name: show message
      debug:
        msg: "my_username  is {{my_username}}, my_password is {{my_password}}"
1
ansible-vault encrypt vault-test-vars.yml
roles放置密码等信息

我们只需要在指定的roles的vars/main.yml放置我们的加密信息

ansible一样可以识别出来的,具体不再演示。

能通过动态触发

既然你已经编辑好很多的模板了,但是你觉得每次都手点也是着实麻烦,或者和 其他系统接入的时候也方便。

那么请参考如下两个文档。

如何认证? 一个简单的方式是

 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
➜  curl -XGET -ku 'username:password' -H "Content-Type: application/json" "http://xxxx.com/api/v2/me/"
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 4,
      "type": "user",
      "url": "/api/v2/users/4/",
      "related": {
        "teams": "/api/v2/users/4/teams/",
        "organizations": "/api/v2/users/4/organizations/",
        "admin_of_organizations": "/api/v2/users/4/admin_of_organizations/",
        "projects": "/api/v2/users/4/projects/",
        "credentials": "/api/v2/users/4/credentials/",
        "roles": "/api/v2/users/4/roles/",
        "activity_stream": "/api/v2/users/4/activity_stream/",
        "access_list": "/api/v2/users/4/access_list/",
        "tokens": "/api/v2/users/4/tokens/",
        "authorized_tokens": "/api/v2/users/4/authorized_tokens/",
        "personal_tokens": "/api/v2/users/4/personal_tokens/"
      },
      "summary_fields": {
        "user_capabilities": {
          "edit": true,
          "delete": false
        }
      },
      "created": "2023-07-20T08:54:24.939084Z",
      "modified": "2023-07-20T09:52:14.256782Z",
      "username": "username",
      "first_name": "user",
      "last_name": "name",
      "email": "",
      "is_superuser": true,
      "is_system_auditor": false,
      "password": "$encrypted$",
      "ldap_dn": "",
      "last_login": "2023-07-20T09:52:14.256782Z",
      "external_account": null,
      "auth": []
    }
  ]
}
触发一个ad-hoc命令

类似 anible -i test all -m shell -a "xxx" 我们可以很轻松的触发一个 command job运行。

 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
88
89
90
91
➜  curl -XPOST -ku 'username:password' -H "Content-Type: application/json" "http://ixxx/api/v2/ad_hoc_commands/?format=json" -d "@data"
{
  "id": 258,
  "type": "ad_hoc_command",
  "url": "/api/v2/ad_hoc_commands/258/",
  "related": {
    "created_by": "/api/v2/users/4/",
    "modified_by": "/api/v2/users/4/",
    "stdout": "/api/v2/ad_hoc_commands/258/stdout/",
    "inventory": "/api/v2/inventories/5/",
    "credential": "/api/v2/credentials/5/",
    "events": "/api/v2/ad_hoc_commands/258/events/",
    "activity_stream": "/api/v2/ad_hoc_commands/258/activity_stream/",
    "notifications": "/api/v2/ad_hoc_commands/258/notifications/",
    "cancel": "/api/v2/ad_hoc_commands/258/cancel/",
    "relaunch": "/api/v2/ad_hoc_commands/258/relaunch/"
  },
  "summary_fields": {
    "inventory": {
      "id": 5,
      "name": "test",
      "description": "",
      "has_active_failures": false,
      "total_hosts": 3013,
      "hosts_with_active_failures": 0,
      "total_groups": 13,
      "has_inventory_sources": true,
      "total_inventory_sources": 1,
      "inventory_sources_with_failures": 0,
      "organization_id": 2,
      "kind": ""
    },
    "credential": {
      "id": 5,
      "name": "test",
      "description": "",
      "kind": "ssh",
      "cloud": false,
      "kubernetes": false,
      "credential_type_id": 1
    },
    "created_by": {
      "id": 4,
      "username": "username",
      "first_name": "user",
      "last_name": "name"
    },
    "modified_by": {
      "id": 4,
      "username": "username",
      "first_name": "user",
      "last_name": "name"
    },
    "user_capabilities": {
      "delete": true,
      "start": true
    }
  },
  "created": "2023-07-21T02:13:59.250766Z",
  "modified": "2023-07-21T02:13:59.263134Z",
  "name": "shell",
  "launch_type": "manual",
  "status": "new",
  "execution_environment": null,
  "failed": false,
  "started": null,
  "finished": null,
  "canceled_on": null,
  "elapsed": 0.0,
  "job_explanation": "",
  "execution_node": "",
  "controller_node": "",
  "launched_by": {
    "id": 4,
    "name": "username",
    "type": "user",
    "url": "/api/v2/users/4/"
  },
  "work_unit_id": null,
  "job_type": "run",
  "inventory": 5,
  "limit": "101.34.158.220",
  "credential": 5,
  "module_name": "shell",
  "module_args": "echo got date $(hostname -I)",
  "forks": 0,
  "verbosity": 0,
  "extra_vars": "{    \"date\":\"20230720\"  }",
  "become_enabled": false,
  "diff_mode": false
}

这个时候我们可以获取对应的258的结果

1
2
3
4
➜ curl -XGET -ku 'username:password' -H "Content-Type: application/json" "http://xxx/api/v2/ad_hoc_commands/258/stdout/?format=txt" 
Identity added: /runner/artifacts/258/ssh_key_data (/runner/artifacts/258/ssh_key_data)
1.1.1.1 | CHANGED | rc=0 >>
got date 1.1.1.1

那在很多填api的地方只能填header怎么办呢,

1
2
echo 'username:password' | base64
curl -H 'Authorization: Basic 你的base64' http://xxx/api/v2/me/

这样测试下就可以了

在awx的命令执行中,你可能希望执行一行命令的时候能够通过extra vars传过 来,默认是不可以在command里面嵌入jinja的,你需要在 xxx.com/#/settings/jobs/details的里面进行设置,把 额外变量何时可以包 含 Jinja 模板 选项设置为always。

也就是这样,我们技术廉价地实现了指令下发功能。

触发job template

如果嫌麻烦,可以直接打开你的awx网页,抓下动态的api就好了。 最终api是这个 http://xxx/api/v2/job_templates/NUMBER/launch/

参考文档