本人只是个前端,很早之前就听说过 ansible, terraform 这类运维工具,一直没机会上手玩一下,上个月借着 GCP 刚开通的 300 刀额度业余时间鼓捣了一下,到现在一个多月了,发现真的是个神器啊,特别是如果有个十来台服务器一起执行 playbook 的时候,有种莫名的爽感😄

一直用一直爽。但 k8s 比这个爽。

是啊是啊,超爽的,我们 1000+台机器,全靠 ansible

问题是,好像个人使用没什么使用场景吧?

等玩腻了 ansible 就折腾 k8s ,其实之前尝试上手过,但是如果不用 GKE 之类的基础设施就太复杂了,而如果用的话又没那种感觉 :) 毕竟我不是专业运维,没有业务需求和压力。很巧的是,我在用一个 ansible wireguard role 的时候看到作者就是纯自己搭建的 k8s ,打算后面跟着他的 blog( www.tauceti.blog/)搞一下

链接有点问题 www.tauceti.blog/posts/kubernetes-the-not-so-hard-way-with-ansible-the-basics/

有个疑问,我想知道你们最多会在多少台机器执行任务啊,如果 1000 台一起执行,控制节点顶得住吗

说实话,没有。我感觉就几台服务器反而会拖慢效率,不如 ssh 上去手敲命令,不过这种工具玩起来就很有趣

#6 看了下监控系统,目前是 1080 台机器。不会同时执行的,我们有不同的 team ,ansible 机器设置了 crontab 错开时间,总共 65 个 team 。倒不是 ansible 机器承受不住,是因为不同 team 配置不同,执行的 role 不同。

至于最多多少台机器执行,这个没法算,我们没有统计每个 team 机器数量。

#3
#6
当你机器配置繁琐的时候,即便数量少,上 ansible 也很轻松。

核心是 jinja2 ,这个熟悉了再会抽配置,就熟练了,很多人都只基础的文件拷贝执行命令啥的

kubespray 是个好东西,正在用它建新集群,参数多多啊。

奥奥,感谢解答,我们使用场景不同,我就一个人玩,和你们团队生产用毕竟还是有很大差距

结合 AI 的话学习起来很快,jinja2 基础用法很简单,就是那些高级的功能比如 Custom Filters 啥的感觉复杂些

看起来很强啊,教程还是用的 GCP ,那我不得不试一试了

#11 我个人的机器也在用 ansible ,包括 k8s 节点的创建,vps 的一些基础配置,比如加 key,创建用户,nfs 挂载,hostname,软件包的安装,有 ansible 会方便很多

作为一个 SRE ,我没怎么用过哈哈哈

---
# tasks file for common

  • name: Account management tasks
    block:

    • name: Ensure group "admin" exists
      ansible.builtin.group:
      name: admin
      gid: 4141
      state: present
    • name: Accounts configuration
      ansible.builtin.user:
      name: "{{ item.name }}"
      uid: "{{ item.uid }}"
      state: present
      group: "{{ item.group }}"
      loop: "{{ accounts }}"
    • name: Set up multiple authorized keys
      ansible.builtin.authorized_key:
      user: "{{ item.user }}"
      key: "{{ item.key }}"
      manage_dir: true
      loop: "{{ keys }}"
    • name: Add sudoers for ansible and hola
      ansible.builtin.lineinfile:
      path: /etc/sudoers.d/systems
      line: "{{ item.name }} ALL=(ALL) NOPASSWD:ALL"
      create: true
      loop: "{{ accounts }}"
      when: item.admin | bool
    • name: Change root password
      ansible.builtin.user:
      name: root
      password: "{{ root_password }}"
      state: present
    • name: Change hola password
      ansible.builtin.user:
      name: hola
      password: "{{ user_password }}"
      state: present
    • name: Install the packages when os is rhel
      ansible.builtin.dnf:
      name: "{{ item.name }}"
      state: "{{ item.state }}"
      loop: "{{ packages_rhel }}"
      when: ansible_os_family == "RedHat"
    • name: Install the packages when os is debian
      ansible.builtin.apt:
      name: "{{ item.name }}"
      state: "{{ item.state }}"
      loop: "{{ packages_debian }}"
      when: ansible_os_family == "Debian" or ansible_os_family == "Ubuntu"
      become: true
      ignore_errors: false
      remote_user: root
      vars:
      ansible_ssh_private_key_file: "~/ansi/key"
  • name: Generate facts
    block:

    • name: Create directory for ansible custom facts
      ansible.builtin.file:
      state: directory
      recurse: true
      path: /etc/ansible/facts.d
    • name: Chcek if exsit custom facts
      ansible.builtin.stat:
      path: /etc/ansible/facts.d/static.fact
      register: host_facts_stat
    • name: Install custom fact
      ansible.builtin.copy:
      src: static.fact
      dest: /etc/ansible/facts.d
      when: not host_facts_stat.stat.exists
    • name: End the play after first time to create custom facts
      meta: end_play
      when: not host_facts_stat.stat.exists

    become: true
    ignore_errors: false
    remote_user: root
    vars:
    ansible_ssh_private_key_file: "~/ansi/key"

  • name: Load custom facts
    ansible.builtin.setup:
    filter: ansible_local
  • name: System configuration tasks
    block:

    • name: Re-read facts after adding custom fact
      ansible.builtin.setup:
      filter: ansible_local

    # Upgrade packages
    # - name: Upgrade all packages for rhel
    # ansible.builtin.dnf:
    # name: "*"
    # state: latest
    # when: ansible_os_family == "RedHat"

    # - name: Upgrade all packages for debian
    # ansible.builtin.apt:
    # name: "*"
    # state: latest
    # when: ansible_os_family == "Debian" or ansible_os_family == "Ubuntu"

    • name: Set hostname
      ansible.builtin.hostname:
      name: "{{ ansible_local'static'['hostname'] }}"
      when: ansible_local'static'['hostname'] is defined and ansible_local'static'['hostname'] != ""
    • name: Configure eth0 ip address
      ansible.builtin.template:
      src: nmconnection_eth0.j2
      dest: /etc/NetworkManager/system-connections/eth0.nmconnection
      owner: root
      group: root
      mode: 0700
      register: nmconnection_eth0_result
    • name: Reload eth0 configuration
      command: |
      nmcli connection reload
      nmcli connection up eth0
      when: nmconnection_eth0_result.changed
    • name: Disable cloud-init network
      ansible.builtin.lineinfile:
      path: /etc/cloud/cloud.cfg
      regexp: '^ renderers'
      insertafter: '^ network:'
      line: " config: disabled"
      when: nmconnection_eth0_result.changed
    • name: Configure eth1 ip address
      ansible.builtin.template:
      src: nmconnection_eth1.j2
      dest: /etc/NetworkManager/system-connections/eth1.nmconnection
      owner: root
      group: root
      mode: 0700
      when: ansible_local'static'['ipaddr_eth1'] is defined and ansible_local'static'['ipaddr_eth1'] != ""
      register: nmconnection_eth1_result
    • name: Reload eth1 configuration
      command: |
      nmcli connection reload
      nmcli connection up eth1
      when: nmconnection_eth1_result.changed

    # - name: Display all variables/facts known for a host
    # debug:
    # var: hostvars[inventory_hostname]
    # tags: debug_info

    • name: Install the packages when os is rhel
      ansible.builtin.dnf:
      name: "{{ item.name }}"
      state: "{{ item.state }}"
      loop: "{{ packages_rhel }}"
      when: ansible_os_family == "RedHat"
    • name: Install the packages when os is debian
      ansible.builtin.apt:
      name: "{{ item.name }}"
      state: "{{ item.state }}"
      loop: "{{ packages_debian }}"
      when: ansible_os_family == "Debian" or ansible_os_family == "Ubuntu"
    • name: Enable atop is enabled and started
      ansible.builtin.systemd_service:
      name: atop
      enabled: true
      state: started
    • name: Disable SELinux persist
      ansible.builtin.selinux:
      state: permissive
      policy: targeted
    • name: Set SELinux in permissive mode at runtime
      command: setenforce 0
    • name: kernel parameters
      ansible.builtin.sysctl:
      name: "{{ item.name }}"
      value: "{{ item.value }}"
      loop: "{{ kernel_parameters }}"
    • name: Update grubby
      command: grubby --update-kernel=ALL --args="net.ifnames=0 biosdevname=0 crashkernel=256M intel_idle.max_cstate=0 processor.max_cstate=1 idle=poll console=tty1 ipv6.disable=1 pci=nommconf pcie_aspm=off mitigations=off"
      when: ansible_os_family == "RedHat"
    • name: Ensure bash profile history lines number is unlimited
      ansible.builtin.lineinfile:
      path: /etc/profile
      regexp: '^HISTSIZE '
      insertafter: '^#HISTSIZE '
      line: HISTSIZE=-1
    • name: Ensure bash profile history file size is unlimited
      ansible.builtin.lineinfile:
      path: /etc/profile
      regexp: '^HISTFILESIZE '
      insertafter: '^#HISTFILESIZE '
      line: HISTFILESIZE=-1

    become: true
    ignore_errors: true

    # tasks file for system configuration

  • block:

    • name: disable SWAP (Kubeadm requirement)
      shell: |
      swapoff -a
    • name: disable SWAP in fstab (Kubeadm requirement)
      replace:
      path: /etc/fstab
      regexp: '^(1.?\sswap\s+sw\s+.)$'
      replace: '# \1'
    • name: create an empty file for the Containerd module
      copy:
      content: ""
      dest: /etc/modules-load.d/containerd.conf
      force: no
    • name: configure modules for Containerd
      blockinfile:
      path: /etc/modules-load.d/containerd.conf
      block: |
      overlay
      br_netfilter
    • name: create an empty file for Kubernetes sysctl params
      copy:
      content: ""
      dest: /etc/sysctl.d/99-kubernetes-cri.conf
      force: no
    • name: configure sysctl params for Kubernetes
      lineinfile:
      path: /etc/sysctl.d/99-kubernetes-cri.conf
      line: "{{ item }}"
      with_items:
    • 'net.bridge.bridge-nf-call-iptables = 1'
    • 'net.ipv4.ip_forward = 1'
    • 'net.bridge.bridge-nf-call-ip6tables = 1'
    • name: apply sysctl params without reboot
      command: sysctl --system
    • name: add Docker's dnf repository
      get_url:
      url: download.docker.com/linux/rhel/docker-ce.repo
      dest: /etc/yum.repos.d/docker-ce.repo
      mode: '0644'
      force: true
    • name: add Kubernetes' dnf repository
      yum_repository:
      name: Kubernetes
      description: Kubernetes
      baseurl: pkgs.k8s.io/core:/stable:/v{{ ansible_local'static'['version'] }}/rpm/
      gpgkey: pkgs.k8s.io/core:/stable:/v{{ ansible_local'static'['version'] }}/rpm/repodata/repomd.xml.key
      enabled: true
      gpgcheck: true
      state: present
    • name: install Containerd
      ansible.builtin.dnf:
      name: containerd.io
      state: present
    • name: create Containerd directory
      file:
      path: /etc/containerd
      state: directory
    • name: add Containerd configuration
      shell: /usr/bin/containerd config default > /etc/containerd/config.toml
    • name: configuring the systemd cgroup driver for Containerd
      lineinfile:
      path: /etc/containerd/config.toml
      regexp: ' SystemdCgroup = false'
      line: ' SystemdCgroup = true'
    • name: enable the Containerd service and start it
      systemd:
      name: containerd
      state: restarted
      enabled: yes
      daemon-reload: yes
    • name: install packages
      dnf:
      name:
    • kubelet
    • kubeadm
    • kubectl
    • iproute-tc
      state: present
      update_cache: true
      register: packages
    • name: download helm script
      ansible.builtin.get_url:
      url: raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
      dest: /tmp/get-helm-3.sh
      mode: '0755'
      force: true
    • name: install helm
      ansible.builtin.shell:
      cmd: /tmp/get-helm-3.sh
    • name: enable the Kubelet service, and enable it persistently
      service:
      name: kubelet
      enabled: yes
    • name: load br_netfilter kernel module
      modprobe:
      name: br_netfilter
      state: present
    • name: set bridge-nf-call-iptables
      sysctl:
      name: net.bridge.bridge-nf-call-iptables
      value: 1
    • name: set ip_forward
      sysctl:
      name: net.ipv4.ip_forward
      value: 1
    • name: reboot and wait for reboot to complete
      reboot:
      when: packages.changed

    GCP 和 AWS 可用不到 kubespray ,纯手搓才用得到。这东西也不是啥好东西,唉,要从 aws 搬家,有得忙了。

    #13 我现在也在把个人服务器迁移为用 ansible 管理,如果是服务器初始化、加固、设置自动化备份之类的重复性任务是方便的,但是比如部署个 vaultwarden 啥的单个服务,由于这种服务我只会部署一个,所以感觉写个 playbook 反而不如直接上去敲命令。不过这只是目前的感受,可能随着服务越来越复杂感受也会变化 :)

    #17 我看这里官方教程用的就是 GCP , github.com/kubernetes-sigs/kubespray/blob/master/docs/getting_started/setting-up-your-first-cluster.md

    ansible 主要问题是太太太慢了,我都搞不明白 Python 怎么能那么慢,五台服务器每次启动任务都要先等个 10 分钟,每个任务执行前后都要卡个四五秒,也不知道在初始化什么东西…… 网络连接肯定没问题的,ssh 也是秒连,服务器本身性能肯定足够

后来换 pyinfra 了,体验还可以: pyinfra.com/

#18 对于你的这个例子,playbook 写的应该是 docker compose 的安装(如果是 docker 的话)以及 wget 你的 docker compose yml 从 git 。

我个人有很多的 vps,不同用途,但基础配置通用,至于个性化的配置写入 ansible 是因为机器由于各种原因需要升级、重建,比如从一个服务商切换到另一个,又或者机器弄乱了,也可能是为了升级。

如果你的机器不多,没有重建的需求,可以只写通用的配置,比如关闭 selinux,关掉 firewalld,禁止 ipv6,创建用户,加 key,对于单个服务,如果是你的生产环境,那么要做好备份。

#15 是这样的,我自己也写了一个 bootstrap 的 role ,用来初始化服务器,和你这个很类似,创建管理员用户、安装基础包、加固 SSH 、修改 dotfiles 等等,确实是用起来相当舒服

#21 你这么说的话倒确实是,我没考虑到服务器到期更换这一点,我目前 netcup 稳如老狗,感觉它不倒我不换 haha

看看我的 homelab ,基于 ansible 的自动 k8s 环境部署和配置。然后用 jsonnet 为何 k8s 的配置。
github.com/aetherrootr/os-environment

#20 我们几十上百台机器跑一个 playbook 获取 facts 都没你 10 分钟这么久,你这个不正常,debug 一下看看卡哪里了吧,命令后面加上-vvv

因为默认要 gather facts 吧,不过我个人感觉速度的优先级很低,自动化执行反而是稳定一点会比较重要

#24 好巧,我也是用 haproxy,不过我是两层,外部 haproxy 给到内部 haproxy

GCP 有直接建的 autopilot ,用这个东西手搓,真是闲的没事干了。

#28 需求不一样,autopilot 不满足

管理个破电脑就这么爽了, 要是让你管理几个人那不得飞起来啊

#23 我最开始是腾讯轻量,后来换到了 aws lightsail, 再然后 hosthatch 黑五时候开了三年的机器,现在转到 ovh dedicate 了。。。

楼上热火朝天的,ansible 的变量有几个级别?这是我被面试的时候问到的问题。

看来你这个前端挺闲的

集群内部的代理无所谓了,我用的比较传统的 Nginx ,在我的应用场景区别不大。

#31 牛的,下一步是 bare mental 哈哈哈

啊?这是面 devops 的岗位吗?问一般的开发感觉有点离谱了。我觉得 ansible 能靠 Google/ai 写出来能跑的配置就行(:

#35 typo mental -> metal

#32 按优先级 roles/defaults 然后 all_vars, group_vars, host_vars, roles/vars, playbook 中的 vars
似乎是这样的,我也不太确定

嗯,我也觉得奇怪,我准确描述一下现象:是忽快忽慢,有时几秒就 gather 完,有时每台都卡几分钟,甚至直接 connection timeout (而这时我自己连接 ssh 一直是稳定、速度正常的)。debug 不太方便做,所有 infra 都迁移出 ansible 了,现在想连还要重配 inventory ,比较麻烦

是的。但调试 ansible playbooks 的时候,肯定不希望每试一个参数都花几分钟时间吧


我也补充下安利 pyinfra 的优点:

  1. 纯 python 作为配置,不需要学奇怪的 yaml 脚本和模板语法,简单的管理完全可以单文件 all-in-one 。现在感觉 ansible 这一套复杂的目录结构只会导致配置文件碎片化,降低可维护性
  2. 速度快,前面提过了,不说了
  3. two-pass 执行,部署任务时可以先快速给你列出哪些任务需要执行、哪些任务可直接 skip ,然后根据实际情况选择执行
  4. 文档简单,API 比 ansible 简易太多

缺点:

  1. 没有 ansible 那么庞大的 roles 库,大部分复杂配置需要自己手敲(不过也敲不了几行,而且都是 Python ,很容易组织起来复用)

    #35 dedicate 就已经是 baremetal 了,而且,我还有一台 colo 放联通机房了

    #24 我发现你居然用的 GL.iNet ,我也有一台,不过是 MT3000