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

推荐订阅源

S
Secure Thoughts
罗磊的独立博客
T
The Blog of Author Tim Ferriss
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
Last Week in AI
Last Week in AI
美团技术团队
Google Online Security Blog
Google Online Security Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
D
Docker
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
酷 壳 – CoolShell
酷 壳 – CoolShell
小众软件
小众软件
月光博客
月光博客
L
LINUX DO - 最新话题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
W
WeLiveSecurity
H
Heimdal Security Blog
Vercel News
Vercel News
SecWiki News
SecWiki News
Forbes - Security
Forbes - Security
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
www.infosecurity-magazine.com
www.infosecurity-magazine.com
TaoSecurity Blog
TaoSecurity Blog
T
Troy Hunt's Blog
A
About on SuperTechFans
C
Check Point Blog
S
Security Affairs
Hacker News - Newest:
Hacker News - Newest: "LLM"
AI
AI
WordPress大学
WordPress大学
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Help Net Security
Help Net Security
博客园_首页
The Last Watchdog
The Last Watchdog
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Engineering at Meta
Engineering at Meta
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
I
Intezer
K
Kaspersky official blog
M
MIT News - Artificial intelligence
J
Java Code Geeks
G
GRAHAM CLULEY
P
Palo Alto Networks Blog

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