惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
WordPress大学
WordPress大学
小众软件
小众软件
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
腾讯CDC
量子位
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
Jina AI
Jina AI
Attack and Defense Labs
Attack and Defense Labs
S
SegmentFault 最新的问题
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Google Online Security Blog
Google Online Security Blog
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
罗磊的独立博客
L
LINUX DO - 最新话题
博客园 - Franky
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
AI
AI
C
Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
I
Intezer
S
Securelist

ParaVocê Dev Blog

How do you deploy in 10 seconds? You're overcomplicating production
How do you provision a Linux VM?
hidden (para · 2024-10-30 · via ParaVocê Dev Blog

There's many ways to provision a Linux VM. I'm going to show you my approach, which as usual, is aimed at keeping it as simple as possible.1

I use Ansible to provision servers. It isn't perfect (it's a huge tool, it's slow, and it uses YAML), but it's the most reliable approach I've found for this task.

The nice advantage to Ansible over running a shell script is all the steps ("tasks" in Ansible parlance) are designed with idempotence in mind. That is you can run the tasks again and again, and it won't change the outcome. This helps you reliably set up consistent servers.

This is really useful when:

  • There's an error partway through its execution and we need to re-run provisioning.
  • You make some changes to the script and want to apply just those changes to your server (even years later).

First we'll need Ansible installed on our server. Enter our trusty friend the shell script, which we'll execute on the remote server:

#!/usr/bin/env bash
set -ex

# Update and upgrade the system
sudo apt update && sudo apt upgrade -y

# Install required packages
sudo apt install -y pipx

# Install Ansible
pipx install ansible

# Add Ansible to PATH if not already there
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
    echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
fi

echo "Initial setup complete. Please update the inventory.ini file with your server's IP address."
echo "Then run the Ansible playbook with: ansible-playbook -i inventory.ini playbook.yaml"

And because I'm forgetful, I have a little addition to my Makefile, so I can remember how to run it:

provision:
	rsync -chazP ansible/provision.sh root@server1:
	ssh root@server1 './provision.sh'
.PHONY: provision

Ansible uses "playbooks," which you can think of as a series of tasks defined in YAML. Let's take a look at how I provision the server by installing updates and configuring SSH:

---
- name: Provision Debian 12
  hosts: all
  become: yes

  vars:
    new_user: "{{ lookup('file', 'user.txt') }}"

  tasks:
    - name: Create non-root user
      user:
        name: "{{ new_user }}"
        groups: sudo
        shell: /bin/bash

    - name: Set authorized keys taken from url
      authorized_key:
        user: "{{ new_user }}"
        state: present
        key: "https://meta.sr.ht/~{{ new_user }}.keys"

    - name: Allow sudo without password for new user
      lineinfile:
        dest: /etc/sudoers
        state: present
        regexp: "^{{ new_user }} ALL="
        line: "{{ new_user }} ALL=(ALL) NOPASSWD: ALL"
        validate: "visudo -cf %s"

    - name: Ensure public key authentication is enabled
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^#?PubkeyAuthentication"
        line: "PubkeyAuthentication yes"
      notify: Restart SSH

    - name: Disable password-based SSH authentication
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^#?PasswordAuthentication"
        line: "PasswordAuthentication no"
      notify: Restart SSH

  handlers:
    - name: Restart SSH
      service:
        name: ssh
        state: restarted

You see lookup('file', 'user.txt') above? That's having Ansible look at my own PC (not the server) for a file named files/user.txt and using that content throughout the rest of the script.

Also notice how it's pulling the authorized keys directly from Sourcehut! You could just as easily do the same from Github with https://github.com/{{ new_user }}.keys, or include them as a file lookup like we did for the user.

I could do all of this in a shell script, but making it idempotent is error prone. I am far too careless of a developer to write perfect shell scripts that account for edge-cases. Ansible gives me a better default behavior for provisioning.

As another example playbook, here's how I run an app with its own daemon user and systemd service file:

---
- name: Provision app
  hosts: all
  become: yes
  vars:
    user: "_app"
    group: "_app"

  tasks:
    - name: Create app group
      group:
        name: "{{ group }}"
        system: yes
        state: present

    - name: Create app user
      user:
        name: "{{ user }}"
        system: yes
        group: "{{ group }}"
        create_home: yes
        home: "/home/{{ user }}"
        shell: /usr/sbin/nologin

    - name: Create systemd service file
      copy:
        content: |
          [Unit]
          Description=app Server
          After=network.target

          [Service]
          Type=simple
          User=_app
          ExecStart=/home/_app/app
          WorkingDirectory=/home/_app
          Restart=always
          RestartSec=5
          Environment=ENV=production
          Environment=ENV_FILE=production.ini

          [Install]
          WantedBy=multi-user.target
        dest: /etc/systemd/system/app.service
        mode: "0644"
        owner: root
        group: root

    - name: Reload systemd
      systemd:
        daemon_reload: yes

    - name: Enable and start app service
      systemd:
        name: app
        enabled: yes
        state: started

In order for Ansible to talk to our server, we need an inventory. A simple inventory.ini file might look like this (you'd replace the IP with your own):

[debian]
100.0.0.1 ansible_user=root

Notice that you can group them under headings. In the playbook above, I used hosts: all, but you could use hosts: debian to target this particular IP. The group names are arbitrary, so you can name them whatever makes sense in your project.

To run this we'd call:

ansible-playbook ansible/provision_app.yaml -i ansible/inventory.ini

We could stop here, but it's often useful to have several playbooks, each performing one task. For instance, I have the following in a simple project:

$ ls ansible/playbooks
provision.yaml # download updates, configure ssh
postgres.yaml  # set up postgres
caddy.yaml     # set up a reverse proxy
app.yaml       # set up a daemon user and enable the service

If we want to run all of these scripts at once instead of individually, we can create playbook.yaml and instruct it to import the others:

- import_playbook: provision.yaml
- import_playbook: postgres.yaml
- import_playbook: caddy.yaml
- import_playbook: app.yaml

And run it just as before:

ansible-playbook ansible/playbook.yaml -i ansible/inventory.ini

That's a quick intro to Ansible. Like Git, Ansible is a huge tool, but I found the "2% of its capabilities" workflow that works for me, and that 2% might be enough for you, too.

In my next post, I'll be describing my journey from horizontal to vertical scaling and why I made the leap. Subscribe on RSS or email to follow along.

  1. For this article I'm assuming Debian. Nix is another great way to reliably create VMs.↩

#devops #sysadmin