OK, This article is a summary from IBM developer Linux series that contains something I haven’t realized and history information about YUM and RPM.
Introducing package management
In the past, many Linux programs were distributed as source code, which a user would build into the required program or set of programs, along with the required man pages, configuration files, and so on. Nowadays, most Linux distributors use prebuilt programs or sets of programs called packages, which ship ready for installation on that distribution. In this tutorial, you will learn about package management tools that help you install, update, and remove packages. This tutorial focuses on the Red Hat Package Manager (RPM), which was developed by Red Hat, as well as the Yellowdog Updater Modified (YUM), which was originally developed to manage Red Hat Linux systems at Duke University’s Physics department. Another tutorial in this series, “Learn Linux 101: Debian package management,” covers the package management tools used on Debian systems.
Package managers
RPM, YUM, and APT (for Debian systems) have many similarities. All can install and remove packages. Information about installed packages is kept in a database. All have basic command-line functionality, while additional tools can provide more user-friendly interfaces. All can retrieve packages from the Internet.
When you install a Linux system, you typically install a large selection of packages. The set may be customized to the intended use of the system, such as a server, desktop, or developer workstation. And at some time you will probably need to install new packages for added functionality, update the packages you have, or even remove packages that you no longer need or that have been made obsolete by newer packages. Let’s look at how you do these tasks, and at some of the related challenges such as finding which package might contain a particular command.
RPM
Red Hat introduced RPM in 1995. RPM is now the package management system used for packaging in the Linux Standard Base (LSB). The rpm command options are grouped into three subgroups for:
Querying and verifying packages
Installing, upgrading, and removing packages
Performing miscellaneous functions
YUM
YUM adds automatic updates and package management, including dependency management, to RPM systems. In addition to understanding the installed packages on a system, YUM is like the Debian Advanced Packaging Tool (APT) in that it works with repositories, which are collections of packages and are typically accessible over a network connection.
Install RPM packages
1 2 3
root@attic‑f21 ~rpm ‑i gcc‑gfortran‑4.9.2‑6.fc21.x86_64.rpm error: Failed dependencies: libquadmath‑devel = 4.9.2‑6.fc21 is needed by gcc‑gfortran‑4.9.2‑6.fc21.x86_64
One good thing is that you can give the rpm command a list of packages to install and it will install them all in the right order if all dependencies are satisfied. So you at least don’t have to manually install each piece in the right order.
Here we see yum find x86_64 version of vim in Local-Base repository. Sometimes you will usually want the latest version of a package, but you can provide additional qualifications if you need an earlier version, or the i686 version instead of the x86_64 version. See the section on specifying package names in the man pages for the yum command.
Package locations
Where do the packages come from? How does yum know where to download packages from? The starting point is the /etc/yum.repos.d/ directory, which usually contains several repo files. This is the default location for repository information, but other locations may be specified in the YUM configuration file, normally /etc/yum.conf.
In the Fyre machine, there is a devit-rh7-x86_64.repo file:
# Base OS packages [Local-Base] name=Fyre Local OS repository baseurl=http://fyreyum1.fyre.ibm.com/redhat/yum/server/7/7Server/x86_64/os gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release gpgcheck=1 enabled=1
[Local-Supplementary] name=Fyre Local Supplementary repository baseurl=http://fyreyum1.fyre.ibm.com/redhat/yum/server/7/7Server/x86_64/supplementary/os gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release gpgcheck=1 enabled=1
[Local-Optional] name=Fyre Local Optional repository baseurl=http://fyreyum1.fyre.ibm.com/redhat/yum/server/7/7Server/x86_64/optional/os gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release gpgcheck=1 enabled=1
[Local-Extras] name=Fyre Local Extras repository baseurl=http://fyreyum1.fyre.ibm.com/redhat/yum/server/7/7Server/x86_64/extras/os gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release gpgcheck=1 enabled=1
YUM and RPM use a local database to determine what packages are installed. The metadata about packages that is stored in the local database is retrieved from the enabled repositories. Although you will seldom need to worry about the local database, you use the command yum clean all to clean out various parts of the locally stored information and yum makecache to create the information in your local database for the enabled repos. You might do this if you change your repo configuration, for example.
Removing RPM packages
The RPM system does not maintain information on packages that were automatically added, so there is no trivial way to find out which dependencies might also be removed.
If you use YUM and if the package you are trying to remove is a dependent package for some other installed packages, then YUM will offer to remove those as well as the dependent package. (This is different from yum autoremove)
Upgrading RPM packages
You can use yum update to update your entire system, or you can specify a single package or a wildcard specification. Listing 8 shows how to update all the packages whose names start with “pop”. Note the use of apostrophes to prevent shell expansion of the “*”.
1 2
yum update 'pop*' yum update elasticsearch-7.10.2
what is the difference between yum update and yum upgrade?yum upgrade forces the removal of obsolete packages, while yum update may or may not also do this. The removal of obsolete packages can be risky, as it may remove packages that you use.
This makes yum update the safer option.
Querying RPM packages
In our examples you saw that installing an rpm with the rpm command requires the full name of the package file (or URL), such as gcc-gfortran-4.9.2-6.fc21.x8664.rpm. On the other hand, installing with yum, or removing an rpm with either command requires only the package name, such as gcc-gfortran. As with APT, YUM maintains an internal database of your installed packages, allowing you to manipulate installed packages using the package name.
Note that you need to have root authority to install, upgrade, or remove packages, but non-root users can perform queries against the rpm database.
Basic query asks if package is installed, if so, show version number:
[root@mycentctl1 ~]# yum info bind-utils Loaded plugins: product-id, search-disabled-repos Installed Packages Name : bind-utils Arch : x86_64 Epoch : 32 Version : 9.9.4 Release : 73.el7_6 Size : 431 k Repo : installed From repo : Local-Base Summary : Utilities for querying DNS name servers URL : http://www.isc.org/products/BIND/ License : ISC Description : Bind-utils contains a collection of utilities for querying DNS (Domain : Name System) name servers to find out information about Internet : hosts. These tools will provide you with the IP addresses for given : host names, as well as other information about registered domains and : network addresses. : : You should install bind-utils if you need to get information from DNS name : servers.
Search package names and descriptions for a term
1 2 3 4 5 6 7 8 9 10 11 12
[root@mycentctl1 ~]# yum search vim Loaded plugins: product-id, search-disabled-repos ================================================= N/S matched: vim ================================================== golang-vim.noarch : Vim plugins for Go protobuf-vim.x86_64 : Vim syntax highlighting for Google Protocol Buffers descriptions vim-X11.x86_64 : The VIM version of the vi editor for the X Window System vim-common.x86_64 : The common files needed by any version of the VIM editor vim-enhanced.x86_64 : A version of the VIM editor which includes recent enhancements vim-filesystem.x86_64 : VIM filesystem layout vim-minimal.x86_64 : A minimal version of the VIM editor
Name and summary matches only, use "search all" for everything.
RPM packages and files in them
List the files inside the package, you will see host command is here:
This module is automatically called by playbooks to gather useful variables
about remote hosts that can be used in playbooks.
1 2 3 4 5
-hosts:all # disable gather facts if necessary gather_facts:no tasks: ...
For example, I can use ansible_memtotal_mb and ansible_processor_vcpus
(processor number in /proc/cpuinfo) to config other daemons, they are both
facts
from remote machine:
1
jvm_heap_size:"{{ ansible_memtotal_mb // 2 | int }}m"
Run task only on the first host in the batch, for example, setting on ES cluster
master. Here provides other options like delegate_to and
when: inventory_hostname == webservers[0].
retry block
By the time Ansible does not
support retry block, but
there is another workaround to implement this useful feature, for instance, to
make group of tasks atomic, see this post:
# PUT method and get response to result -name:disableshardallocation uri: url:http://localhost:9200/_cluster/settings?format=json method:PUT status_code:200 body_format:json return_content:yes headers: Content-Type:application/json body:"{\"persistent\": {\"cluster.routing.allocation.enable\": \"primaries\"}}" # if request response is slow timeout:900 register:result
-name:displayresponsebody debug: var:result.json
# if reponse is an array of dicts -name:Printcertainelement debug: var:result.json[0].xxx.yyy
Pauses playbook execution for a set amount of time, or until a prompt is
acknowledged. All parameters are optional. The default behavior is to pause with
a prompt.
The pause module integrates into async/parallelized playbooks without any
special considerations (see Rolling Updates). When using pauses with the serial
playbook parameter (as in rolling updates) you are only prompted once for the
current group of hosts.
Useful when debug certain task to see the execution result:
Useful for debugging together with the when: directive.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
## Example that prints return information from the previous task -shell:/usr/bin/uptime register:result
## var option already runs in Jinja2 context and has an implicit {{ }} wrapping -debug: var:result ## this verbosity is associated with `-vv` parameter verbosity:2
## prints the loopback address and gateway for each host -debug: msg:System {{ inventory_hostname }} hasgateway {{ ansible_default_ipv4.gateway }} when:ansible_default_ipv4.gatewayisdefined
## fail the process if the retry failed -fail: msg:Thetimelimithit,theclustermaynotbeinreadystatus! ## it depends what output is in the register variable when:job_result.failed==true
copy
https://docs.ansible.com/ansible/latest/modules/copy_module.html
The copy module copies a file from the local or remote machine to a location on
the remote machine (depends on the condition). 和template类似, 如果task下面有files
文件夹, 在不指定src路径的时候, eg: src: xxx.txt, 会从files文件夹里copy.
Use the fetch module to copy files from remote locations to the local box.
If you need variable interpolation in copied files, use the template module.
Using a variable in the content field will result in unpredictable output.
# {{ baseDir }}/registry-certs/tls.crt is in control machine -name:Copysecuredockerregistryssl/tlscertstoallworkernodes any_errors_fatal:true copy: src:"{{ baseDir }}/registry-certs/tls.crt" dest:"/etc/docker/certs.d/{{ service name }}:5000/tls.crt" owner:root group:root mode:'0644'
# remote_src: yes means copy is happening on remote machine, the src is # also in that remote machine. # remote_src supports recursive copying as of version 2.8 -name:Copya"sudoers"fileontheremotemachineforediting copy: src:/etc/sudoers dest:/etc/sudoers.edit remote_src:yes
# With delegate_to, it copies local ssh_key.path file(from control machine) to # that fileserver_ip. -name:CopySSHkeytofileserverforSCPuse no_log:"{{ task_no_log }}" copy: src:"{{ ssh_key.path }}" dest:"/tmp/ns1.pk" owner:root group:root mode:'0400' delegate_to:"{{ fileserver_ip }}"
NOTE that mode: '0400' must use 4 digits, such as 0644 instead of 644,
otherwise you write sticky bit. See
ansible copy make sticky bit.
Files that already exist at dest will be overwritten if they are different than
the src.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# fetched file is marked by the remote hostname -name:Storefileinto/tmp/fetched/<srcmachinehostname>/tmp/somefile fetch: src:/tmp/somefile dest:/tmp/fetched
# If dest ends with '/', it will use the basename of the source file, similar to # the copy module. # This can be useful if working with a single host, or if retrieving files that # are uniquely named per host. # If using multiple hosts with the same filename, the file will be overwritten # for each host. -name:Specifyingadestinationpath fetch: src:/tmp/uniquefile dest:/tmp/special/ flat:yes
Templates are processed by the Jinja2 templating language.
Documentation on the template formatting can be found in the
Template Designer Documentation.
When template works it picks source file from role’s templates/ folder.
If the template file contains jinja2 placeholder, it will be interpolated.
1 2 3 4 5 6 7 8
# Copy from control machine to target nodes -name:Templateafileto/etc/files.conf template: src:/mytemplates/foo.j2 dest:/etc/file.conf owner:bin group:wheel mode:'0644'
# the symbol | is a Yaml formater -name:CopyIISdockerimagestospecifiedworkernodes any_errors_fatal:true shell:| /tmp/copyIISDockers.sh {{ iisDockers }} {{ imageTag }} if [[ $? -eq 0 ]]; then touch /tmp/copyIISImageToWorker.done fi when:"inventory_hostname == groups.master[0]" args: ## A filename, when it already exists, this step will not be run. creates:/tmp/copyIISImageToWorker.done ## A filename, when it does not exist, this step will not be run. removes:/tmp/preTaskOk.done ## disable task warning warn:no ## change the shell executable:/bin/bash
The command(s) will not be processed through the shell, so variables like
$HOME and operations like “<”, “>”, “|”, “;” and “&” will not work. Use the
shell module if you need these features.
# 'args' is a task keyword, passed at the same level as the module -name:Runcommandif/path/to/databasedoesnotexist(with'args'keyword). command:/usr/bin/make_database.shdb_userdb_name args: creates:/path/to/database
# 'cmd' is module parameter -name:Runcommandif/path/to/databasedoesnotexist(with'cmd'parameter). command: cmd:/usr/bin/make_database.shdb_userdb_name creates:/path/to/database
If you use sed in command module, you will get warning, you can disable the
warning by add warn: false or use lineinfile module.
This module ensures a particular line is in a file, or replace an existing line
using a back-referenced regular expression.
This is primarily useful when you want to change a single line in a file only.
See the replace module if you want to change multiple, similar lines or check
blockinfile if you want to insert/update/remove a block of lines in a file.
For other cases, see the copy or template modules.
If the vars expresson is long, can modify it into multipe lines:
1 2 3 4 5 6 7 8
# using > instead of |, > will ignore the line return ane assemble the # multi-line into one line. result:> "{{ ((groups['data'] | length) == 0 and (groups['data_hot'] | length) == 0 and (groups['data_warm'] | length) == 0) | ternary('it is OK', 'bad!') }}"
Could we remove major architectural components from the IT automation stack? Eliminating management daemons and relying instead on OpenSSH meant the system could start managing a computer fleet immediately, without having to set up anything on the managed machines.
the “Making Ansible Go Even Faster” chapter now covers asynchronous tasks, and the “Debugging Ansible Playbooks” chapter now covers the debugger that was introduced in version 2.1.
we are all slowly turning into system engineers.
Chapter 1. Introduction
When we talk about configuration management, we are typically talking about writing some kind of state description for our servers, and then using a tool to enforce that the servers are, indeed, in that state: the right packages are installed, configuration files contain the expected values and have the expected permissions, the right services are running, and so on.
Ansible is a great tool for deployment as well as configuration management. Using a single tool for both configuration management and deployment makes life simpler for the folks responsible for operations.
Some people talk about the need for orchestration of deployment. This is where multiple remote servers are involved, and things have to happen in a specific order.
How Ansible works
In Ansible, a script is called a playbook. A playbook describes which hosts (what Ansible calls remote servers) to configure, and an ordered list of tasks to perform on those hosts.
Ansible will make SSH connections in parallel to web1, web2, and web3. It will execute the first task on the list on all three hosts simultaneously
To manage a remote server with Ansible, the server needs to have SSH and Python 2.5 or later installed, or Python 2.4 with the Python simplejsonlibrary installed. There’s no need to preinstall an agent or anyother software on the host.
The control machine (the one that you use to control remote machines) needs to have Python 2.6 or later installed.
Ansible is push based, and has been used successfully in production with thousands of nodes, and has excellent support for environments where servers are dynamically added and removed.
Ansible modules are declarative; you use them to describe the state you want the server to be in. Modules are also idempotent.
Ansible has excellent support for templating, as well as defining variables at different scopes. Anybody who thinks Ansible is equivalent to working with shell scripts has never had to maintain a nontrivial program written in shell. I’ll always choose Ansible over shell scripts for config management tasks if given a choice.
To be productive with Ansible, you need to be familiar with basic Linux system administration tasks. Ansible makes it easy to automate your tasks, but it’s not the kind of tool that “automagically” does things that you otherwise wouldn’t know how to do.
Ansible uses the YAML file format and the Jinja2 templating languages, so you’ll need to learn some YAML and Jinja2 to use Ansible, but both technologies are easy to pick up.
If you prefer not to spend the money on a public cloud, I recommend you install Vagrant on your machine. Vagrant is an excellent open source tool for managing virtual machines. You can use Vagrant to boot a Linux virtual machine inside your laptop, and you can use that as a test server.
Vagrant needs the VirtualBox virtualizer to be installed on your machine. Download VirtualBox and then download Vagrant.
1 2 3 4
mkdir playbooks cd playbooks vagrant init ubuntu/trusty64 vagrant up
The first time you use vagrant up, it will download the virtual machine image file, which might take a while, depending on your internet connection.
You should be able to SSH into your new Ubuntu 14.04 virtual machine by running the following:
1
vagrant ssh
This approach lets us interact with the shell, but Ansible needs to connect to the virtual machine by using the regular SSH client, not the vagrant ssh command.
Tell Vagrant to output the SSH connection details by typing the following:
Ansible supports the ssh-agent program, so you don’t need to explicitly specify SSH key files in your inventory files. See “SSH Agent” for more details if you haven’t used ssh-agent before
If Ansible did not succeed, add the -vvvv flag to see more details about the error:
1
ansible testserver -i hosts -m ping -vvvv
Simplify by ansible.cfg file
we’ll use one such mechanism, the ansible.cfg file, to set some defaults so we don’t need to type as much.
Ansible looks for an ansible.cfg file in the following places, in this order:
File specified by the ANSIBLE_CONFIG environment variable
./ansible.cfg (ansible.cfg in the current directory)
~/.ansible.cfg (.ansible.cfg in your home directory)
/etc/ansible/ansible.cfg
I typically put ansible.cfg in the current directory, alongside my playbooks. That way, I can check it into the same version-control repository that my playbooks are in.
Disables SSH host-key checking. Otherwise, we need to edit our ~/.ssh/known_hosts file every time we destroy and re-create a nodes.
Ansible uses /etc/ansible/hosts as the default location for the inventory file. However, I never use this because I like to keep my inventory files version-controlled alongside my playbooks.
The command module is so commonly used that it’s the default module, so we can omit it
1 2 3 4 5
ansible testserver -a uptime ## spaces in command use quotes ansible testserver -a "tail /var/log/dmesg" ## -b becomes root user ansible testserver -b -a "tail /var/log/syslog"
Chapter 2. Playbooks: A Beginning
Most of your time in Ansible will be spent writing playbooks. A playbook is the term that Ansible uses for a configuration management script.
vargant virtual machine ports mapping in vagrantfile:
1 2 3 4 5 6 7
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "ubuntu/trusty64" config.vm.network "forwarded_port", guest:80, host:8080 config.vm.network "forwarded_port", guest:443, host:8443 end
You might be familiar with the term SSL rather than TLS(Transport Layer Security) in the context of secure web servers. SSL is an older protocol that was used to secure communications between browsers and web servers, and it has been superseded by a newer protocol named TLS. Although many continue to use the term SSL to refer to the current secure protocol, in this book, I use the more accurate TLS.
WHY DO YOU USE TRUE IN ONE PLACE AND YES IN ANOTHER?
Strictly speaking, module arguments (for example, update_cache=yes) are treated differently from values elsewhere in playbooks (for example, sudo: True). Values elsewhere are handled by the YAML parser and so use the YAML conventions of truthiness:
YAML truthy
true, True, TRUE, yes, Yes, YES, on, On, ON, y, Y
YAML falsey
false, False, FALSE, no, No, NO, off, Off, OFF, n, N
Module arguments are passed as strings and use Ansible’s internal conventions:
module arg truthy
yes, on, 1, true
module arg falsey
no, off, 0, false
I tend to follow the examples in the official Ansible documentation. These typically use yes and no when passing arguments to modules (since that’s consistent with the module documentation), and True and False elsewhere in playbooks.
NOTE
An Ansible convention is to keep files in a subdirectory named files, and Jinja2 templates in a subdirectory named templates. I follow this convention throughout the book.
For .j2 file, when Ansible renders this template, it will replace this variable with the real value.
< PLAY [Configure webserver with nginx] > --------------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
If you don’t want to see the cows, you can disable cowsay by setting the ANSIBLE_NOCOWS environment variable like this:
1
export ANSIBLE_NOCOWS=1
You can also disable cowsay by adding the following to your ansible.cfg file:
1 2
[defaults] nocows = 1
TIP
If your playbook file is marked as executable and starts with a line that looks like this
1
#!/usr/bin/env ansible-playbook
then you can execute it by invoking it directly, like this:
1
./playbook-file.yml
YAML syntax
Start of file
1
---
However, if you forget to put those three dashes at the top of your playbook files, Ansible won’t complain.
String
In general, YAML strings don’t have to be quoted, although you can quote them if you prefer. Even if there are spaces, you don’t need to quote them.
In some scenarios in Ansible, you will need to quote strings. These typically involve the use of for variable substitution.
Boolean
YAML has a native Boolean type, and provides you with a wide variety of strings that can be interpreted as true or false.
List
1 2 3
- My Fair Lady - Oklahoma - The Pirates of Penzance
Dictionary
1 2 3
address: 742 Evergreen Terrace city: Springfield state: North Takoma
Line folding
When writing playbooks, you’ll often encounter situations where you’re passing many arguments to a module. For aesthetics, you might want to break this up across multiple lines in your file, but you want Ansible to treat the string as if it were a single line.
You can do this with YAML by using line folding with the greater than (>) character. The YAML parser will replace line breaks with spaces. For example:
1 2 3 4 5 6
address: > Department of Computer Science, A.V. Williams Building, University of Maryland city: College Park state: Maryland
you’ll see in Chapter 16, you can use the --start-at-task <task name> flag to tell ansible-playbook to start a playbook in the middle of a play, but you need to reference the task by name.
Every task must contain a key with the name of a module and a value with the arguments to that module. In the preceding example, the module name is apt and the arguments are name=nginx update_cache=yes.
The arguments are treated as a string, not as a dictionary. This means that if you want to break arguments into multiple lines, you need to use the YAML folding syntax, like this:
Ansible also supports a task syntax that will let you specify module arguments as a YAML dictionary, which is helpful when using modules that support complex arguments. For example:
Modules are scripts that come packaged with Ansible and perform some kind of action on a host.
commonly used modules:
yum
copy
file
service
template
VIEWING ANSIBLE MODULE DOCUMENTATION
Ansible ships with the ansible-doc command-line tool, which shows documentation about modules. Think of it as man pages for Ansible modules.
1
ansible-doc yum
Recall from the first chapter that Ansible executes a task on a host by generating a custom script based on the module name and arguments, and then copies this script to the host and runs it.
More than 200 modules ship with Ansible, and this number grows with every release. You can also find third-party Ansible modules out there, or write your own.
Putting It All Together
To sum up, a playbook contains one or more plays. A play associates an unordered set of hosts with an ordered list of tasks. Each task is associated with exactly one module.
Ansible modules will first check to see whether the state of the host needs to be changed before taking any action. If the state of the host matches the arguments of the module, Ansible takes no action on the host and responds with a state of ok.
Ansible’s detection of state change can be used to trigger additional actions through the use of handlers. But, even without using handlers, it is still a useful form of feedback to see whether your hosts are changing state as the playbook runs.
In a production environment, you’d purchase your TLS certificate from a certificate authority, or use a free service such as Let’s Encrypt, which Ansible supports via the letsencrypt module.
Here we use self-signed certificate generated free of charge:
Any valid YAML can be used as the value of a variable. You can use lists and dictionaries in addition to strings and Booleans.
Variables
Variables can be used in tasks, as well as in template files. You reference variables by using the {{ braces }} notation. Ansible replaces these braces with the value of the variable.
WHEN QUOTING IS NECESSARY
bad:
1 2
- name: perform some task command: {{ myapp }} -a foo
good:
1 2
- name: perform some task command: "{{ myapp }} -a foo"
A similar problem arises if your argument contains a colon. For example, bad:
1 2
- name: show a debug message debug: msg="The debug module will print a message: neat, eh?"
good:
1 2
- name: show a debug message debug: "msg='The debug module will print a message: neat, eh?'"
Generating the Template
We put templates in templates folder, we use the .j2 extension to indicate that the file is a Jinja2 template. However, you can use a different extension if you like; Ansible doesn’t care.
You can use all of the Jinja2 features in your templates, you probably won’t need to use those advanced templating features, though. One Jinja2 feature you probably will use with Ansible is filters: Jinja2 Template Designer Documentation.
Handlers
Handlers are one of the conditional forms that Ansible supports. A handler is similar to a task, but it runs only if it has been notified by a task. A task will fire the notification if Ansible recognizes that the task has changed the state of the system. A task notifies a handler by passing the handler’s name as the argument.
A FEW THINGS TO KEEP IN MIND ABOUT HANDLERS
Handlers usually run after all of the tasks are run at the end of the play. They run only once, even if they are notified multiple times. If a play contains multiple handlers, the handlers always run in the order that they are defined in the handlers section, not the notification order.
The official Ansible docs mention that the only common uses for handlers are for restarting services and for reboots. Personally, I’ve always used them only for restarting services.Even then, it’s a pretty small optimization, since we can always just unconditionally restart the service at the end of the playbook instead of notifying it on change, and restarting a service doesn’t usually take very long.
Chapter 3. Inventory: Describing Your Servers
The collection of hosts that Ansible knows about is called the inventory. In this chapter, you will learn how to describe a set of hosts as an Ansible inventory.
Ansible automatically adds one host to the inventory by default: localhost. Ansible understands that localhost refers to your local machine, so it will interact with it directly rather than connecting by SSH.
Preliminaries: Multiple Vagrant Machines
Before you modify your existing Vagrantfile, make sure you destroy your existing virtual machine by running the following:
| Name | Default | Description | |:--------------------------: |:---------------: |:-------------------------------------------------------------------------------: | | ansible_host | Name of host | Hostname or IP address to SSH to | | ansible_port | 22 | Port to SSH to | | ansible_user | Root | User to SSH as | | ansible_password | (None) | Password to use for SSH authentication | | ansible_connection | smart | How Ansible will connect to host (see the following section) | | ansible_private_key_file | (None) | SSH private key to use for SSH authenticatio | | ansible_shell_type | sh | Shell to use for commands (see the following section) | | ansible_python_interpreter | /usr/bin/python | Python interpreter on host (see the following section) | | ansible_*_interpreter | (None) | Like ansible_python_interpreter for other languages (see the following section) |
Explanation:
ansible_connection
Ansible supports multiple transports, which are mechanisms that Ansible uses to connect to the host. The default transport, smart, will check whether the locally installed SSH client supports a feature called ControlPersist. If the SSH client supports ControlPersist, Ansible will use the local SSH client. If the SSH client doesn’t support ControlPersist, the smart transport will fall back to using a Python-based SSH client library called Paramiko.
ansible_shell_type
Ansible works by making SSH connections to remote machines and then invoking scripts. By default, Ansible assumes that the remote shell is the Bourne shell located at /bin/sh, and will generate the appropriate command-line parameters that work with Bourne shell.
Ansible also accepts csh, fish, and (on Windows) powershell as valid values for
this parameter. I’ve never encountered a need for changing the shell type.
ansible_python_interpreter
Because the modules that ship with Ansible are implemented in Python 2, Ansible needs to know the location of the Python interpreter on the remote machine. You might need to change this if your remote host does not have a Python 2 interpreter at /usr/bin/python. For example, if you are managing hosts that run Arch Linux, you will need to change this to /usr/bin/python2, because Arch Linux installs Python 3 at /usr/bin/python, and Ansible modules are not (yet) compatible with Python 3.
ansible_*_interpreter
If you are using a custom module that is not written in Python, you can use this parameter to specify the location of the interpreter (e.g., /usr/bin/ruby).
Note: You can override some of the behavioral parameter default values in the defaults section of the ansible.cfg file
Ansible automatically defines a group called all (or *), which includes all of the hosts in the inventory. For example:
1 2
ansible all -a "date" ansible '*' -a "date"
We can define our own groups in the inventory file. Ansible uses the .ini file format for inventory files. In the .ini format, configuration values are grouped together into sections. For example:
Ansible also allows you to define groups that are made up of other groups. Here web and task are groups, diango subgroup wrap them up.
1 2 3
[django:children] web task
Numbered hosts
1 2 3 4 5
[host] web[1:20].example.com ## leading 0 web[01:20].example.com web-[a-t].example.com
Host and Group Variables
Format: [:vars]
1 2 3 4 5
[all:vars]
[production:vars]
[staging:vars]
Additionally, though Ansible variables can hold Booleans, strings, lists, and dictionaries, in an inventory file, you can specify only Booleans and strings.
Ansible offers a more scalable approach to keep track of host and group variables: you can create a separate variable file for each host and each group. Ansible expects these variable files to be in YAML format.
Ansible looks for host variable files in a directory called host_vars and group variable files in a directory called group_vars. Ansible expects these directories to be either in the directory that contains your playbooks or in the directory adjacent to your inventory file. For example:
If the inventory file is marked executable, Ansible will assume it is a dynamic inventory script and will execute the file instead of reading it.
If some other system, such as AWS EC2, will keep track of the virtual machine information for us, we don’t necessarily need to write the inventory file manually, we can use dynamic inventory script to query about which machines are running and use them.
Adding Entries at Runtime with add_host and group_by
Ansible will let you add hosts and groups to the inventory during the execution of a playbook.
not use yet
Chapter 4. Variables and Facts
Ansible is not a full-fledged programming language, but it does have several programming language features, and one of the most important of these is variable substitution. This chapter presents Ansible’s support for variables in more detail, including a certain type of variable that Ansible calls a fact.
## or -name:ConfigureKubeadm hosts:master become:true any_errors_fatal:true vars_files: -config.yml roles: -setup.master
Viewing the Values of Variables
We use the debug module to print out an arbitrary message. We can also use it to output the value of the variable.
1
- debug: var=<myvarname>
Registering Variables
Often, you’ll find that you need to set the value of a variable based on the result of a task.To do so, we create a registered variable using the register clause when invoking a module. Below shows how to capture the output of the whoami command to a variable named login.
Note, if you want to use login variable registered here, it’s not like that you can call it as {{ login }}
In order to use the login variable later, we need to know the type of value to expect. The value of a variable set using the register clause is always a dictionary, but the specific keys of the dictionary are different, depending on the module that was invoked.
Unfortunately, the official Ansible module documentation doesn’t contain information about what the return values look like for each module. The module docs do often contain examples that use the register clause, which can be helpful. I’ve found the simplest way to find out what a module returns is to register a variable and then output that variable with the debug module.
1 2 3 4 5 6 7
- name: show return value of command module hosts: server1 tasks: - name: capture output of id command command: id -un register: login - debug: var=login
The shell module has the same output structure as the command module, but other modules contain different keys, the output here is:
The changed key is present in the return value of all Ansible modules, and Ansible uses it to determine whether a state change has occurred. For the command and shellmodule, this will always be set to true unless overridden with the changed_whenclause.
The cmd key contains the invoked command as a list of strings.
The rc key contains the return code. If it is nonzero, Ansible will assume the task failed to execute.
The stdout key contains any text written to standard out, as a single string.
The stdout_lines key contains any text written to split by newline. It is a list, and each element of the list is a line of output.
So now you can access login with:
1 2 3 4
- name: capture output of id command command: id -un register: login - debug: msg="Logged in as user {{ login.stdout }}"
ACCESSING DICTIONARY KEYS IN A VARIABLE
If a variable contains a dictionary, you can access the keys of the dictionary by using either a dot (.) or a subscript ([]).
If your playbooks use registered variables, make sure you know the content of those variables, both for cases where the module changes the host’s state and for when the module doesn’t change the host’s state. Otherwise, your playbook might fail when it tries to access a key in a registered variable that doesn’t exist.
Facts
As you’ve already seen, when Ansible runs a playbook, before the first task runs, this happens:
When Ansible gathers facts, it connects to the host and queries it for all kinds of details about the host: CPU architecture, operating system, IP addresses, memory info, disk info, and more. This information is stored in variables that are called facts, and they behave just like any other variable. For example:
1 2 3 4 5
- name: print out operating system hosts: all gather_facts: True tasks: - debug: var=ansible_distribution
Ansible implements fact collecting through the use of a special module called the setupmodule. You don’t need to call this module in your playbooks because Ansible does that automatically when it gathers facts.
Note that the returned value is a dictionary whose key is ansible_facts and whose value is a dictionary that contains the name and value of the actual facts.
Viewing a Subset of Facts
The setup module supports a filter parameter that lets you filter by fact name by specifying a glob.
1
ansible web -m setup -a 'filter=ansible_eth*'
Any Module Can Return Facts
The use of ansible_facts in the return value is an Ansible idiom. If a module returns a dictionary that contains ansible_facts as a key, Ansible will create variable names in the environment with those values and associate them with the active host.
For modules that return facts, there’s no need to register variables, since Ansible creates these variables for you automatically.
1 2 3 4
- name: get ec2 facts ec2_facts:
- debug: var=ansible_ec2_instance_id
Several modules ship with Ansible that return facts. You’ll see another one of them, the docker module.
Local Facts
Ansible provides an additional mechanism for associating facts with a host. You can place one or more files on the remote host machine in the /etc/ansible/facts.d directory.
not used yet
Using set_fact to Define a New Variable
Ansible also allows you to set a fact (effectively the same as defining a new variable) in a task by using the set_fact module. I often like to use set_fact immediately after register to make it simpler to refer to a variable.
1 2 3 4 5 6 7 8 9 10 11
- name: get snapshot id shell: > aws ec2 describe-snapshots --filters Name=tag:Name,Values=my-snapshot | jq --raw-output ".Snapshots[].SnapshotId" register: snap_result
Ansible defines several variables that are always available in a playbook
1 2 3 4 5 6 7 8 9 10 11
| Parameter | Description | |-------------------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | hostvars | A dict whose keys are Ansible hostnames and values are dicts that map variable names to values | | inventory_hostname | Fully qualified domain name of the current host as known by Ansible (e.g., myhost.example.com) | | inventory_hostname_short | Name of the current host as known by Ansible, without the domain name (e.g., myhost) | | group_names | A list of all groups that the current host is a member of | | groups | A dict whose keys are Ansible group names and values are a list of hostnames that are members of the group. Includes all and ungrouped groups: {"all": […], "web": […], "ungrouped": […]} | | ansible_check_mode | A boolean that is true when running in check mode | | ansible_play_batch | A list of the inventory hostnames that are active in the current batch | | ansible_play_hosts | A list of all of the inventory hostnames that are active in the current play | | ansible_version | A dict with Ansible version info: {"full": 2.3.1.0", "major": 2, "minor": 3, "revision": 1, "string": "2.3.1.0"} |
hostvars
This is a dictionary that contains all of the variables defined on all of the hosts, keyed by the hostname as known to Ansible. If Ansible has not yet gathered facts on a host, you will not be able to access its facts by using the hostvars variable, unless fact caching is enabled.
Variables set by passing -e var=value to ansible-playbook have the highest precedence, which means you can use this to override variables that are already defined.
Ansible also allows you to pass a file containing the variables instead of passing them directly on the command line by passing @filename.yml as the argument to -e
1
ansible-playbook greet.yml -e @greetvars.yml
content in greetvars.yml:
1
greeting: hiya
Precedence
We’ve covered several ways of defining variables, and it can happen that you define the same variable multiple times for a host, using different values. Avoid this when you can, but if you can’t, then keep in mind Ansible’s precedence rules.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
1. (Highest) ansible-playbook -e var=value 2. Task variables 3. Block variables 4. Role and include variables 5. set_fact 6. Registered variables 7. vars_files 8. vars_prompt 9. Play variables 10. Host facts 11. host_vars set on a playbook 12. group_vars set on a playbook 13. host_vars set in the inventory 14. group_vars set in the inventory 15. Inventory variables 16. In defaults/main.yml of a role
Chapter 5. Introducing Mezzanine: Our Test Application
Let’s take a little detour and talk about the differences between running software in development mode on your laptop versus running the software in production. Mezzanine is a great example of an application that is much easier to run in development mode than it is to deploy.
You’ll be prompted to answer several questions. I answered “yes” to each yes/no question, and accepted the default answer whenever one was available.
Now, let’s look at what happens when you deploy to production.
PostgreSQL: The Database
In production, we want to run a server-based database, because those have better support for multiple, concurrent requests, and server-based databases allow us to run multiple HTTP servers for load balancing. This means we need to deploy a database management system such as MySQL or PostgreSQL (aka Postgres).
1 2 3 4 5 6
Install the database software. Ensure the database service is running. Create the database inside the database management system. Create a database user who has the appropriate permissions for the database system. Configure our Mezzanine application with the database user credentials and connection information.
Gunicorn: The Application Server
Nginx: The Web Server
Note: Application server and Web server, their usage is different. Here Nginx is like a reverse proxy for Gunicorn.
Supervisor: The Process Manager
Chapter 7. Roles: Scaling Up Your Playbooks
Ansible scales down well because simple tasks are easy to implement. It scales up well because it provides mechanisms for decomposing complex jobs into smaller pieces.
In Ansible, the role is the primary mechanism for breaking a playbook into multiple files. This simplifies writing complex playbooks, and it makes them easier to reuse.
Basic Structure of a Role
An Ansible role has a name, such as database. Files associated with the database role go in the roles/database directory, which contains the following files and directories:
1 2 3 4 5 6 7
| roles/database/tasks/main.yml | Tasks | | roles/database/files/ | Holds files to be uploaded to hosts | | roles/database/templates/ | Holds Jinja2 template files | | roles/database/handlers/main.yml | Handlers | | roles/database/vars/main.yml | Variables that shouldn’t be overridden | | roles/database/defaults/main.yml | Default variables that can be overridden | | roles/database/meta/main.yml | Dependency information about a role |
Each individual file is optional; if your role doesn’t have any handlers, there’s no need to have an empty handlers/main.yml file.
WHERE DOES ANSIBLE LOOK FOR MY ROLES?
Ansible looks for roles in the roles directory alongside your playbooks. It also looks for systemwide roles in /etc/ansible/roles. You can customize the systemwide location of roles by setting the roles_path setting in the defaults section of your ansible.cfg file.
1 2
[defaults] roles_path = ~/ansible_roles
Using Roles in Your Playbooks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
- name: deploy mezzanine on vagrant ## target hosts hosts: web vars_files: - secrets.yml ## role section roles: - role: database ## pass variables into role task database_name: "{{ mezzanine_proj_name }}" database_user: "{{ mezzanine_proj_name }}"
Note that we can pass in variables when invoking the roles. If these variables have already been defined in the role (either in vars/main.yml or defaults/main.yml), then the values will be overridden with the variables that were passed in.
Pre-Tasks and Post-Tasks
Sometimes you want to run tasks before or after you invoke your roles. Let’s say you want to update the apt cache before you deploy Mezzanine, and you want to send a notification to a Slack channel after you deploy.
Ansible allows you to define a list of tasks that execute before the roles with a pre_tasks section, and a list of tasks that execute after the roles with a post_taskssection.
Note: you need to define and put variables in right place, for example, the variables that would be used by multiple roles or playbooks should be put in group_vars/all file.
WHY ARE THERE TWO WAYS TO DEFINE VARIABLES IN ROLES?
When Ansible first introduced support for roles, there was only one place to define role variables, in vars/main.yml. Variables defined in this location have a higher precedence than those defined in the vars section of a play, which meant you couldn’t override the variable unless you explicitly passed it as an argument to the role.
Ansible later introduced the notion of default role variables that go in defaults/main.yml.This type of variable is defined in a role, but has a low precedence, so it will be overridden if another variable with the same name is defined in the playbook.
If you think you might want to change the value of a variable in a role, use a default variable. If you don’t want it to change, use a regular variable.
Some role practices
Note that if for role variables, it’s better to add prefix like <role name>_<var name>. It’s good practice to do this with role variables because Ansible doesn’t have any notion of namespace across roles. This means that variables that are defined in other roles, or elsewhere in a playbook, will be accessible everywhere. This can cause some unexpected behavior if you accidentally use the same variable name in two different roles. For example, for the role called mezzanine, in roles/mezzanine/vars/main.yml file:
Note: there’s one important difference between tasks defined in a role and tasks defined in a regular playbook, and that’s when using the copy or template modules.
When invoking copy in a task defined in a role, Ansible will first check the rolename/files/ directory for the location of the file to copy. Similarly, when invoking template in a task defined in a role, Ansible will first check the rolename/templates directory for the location of the template to use.
This means that a task that used to look like this in a playbook:
1 2 3
- name: set the nginx config file template: src=templates/nginx.conf.j2 \ dest=/etc/nginx/sites-available/mezzanine.conf
now looks like this when invoked from inside the role (note the change of the src parameter):
1 2 3
- name: set the nginx config file template: src=nginx.conf.j2 dest=/etc/nginx/sites-available/mezzanine.conf notify: restart nginx
Creating Role Files and Directories with ansible-galaxy
Ansible ships with another command-line tool we haven’t talked about yet, ansible-galaxy. Its primary purpose is to download roles that have been shared by the Ansible community. But it can also be used to generate scaffolding, an initial set of files and directories involved in a role:
Ansible supports a feature called dependent roles to deal with this scenario. When you define a role, you can specify that it depends on one or more other roles. Ansible will ensure that roles that are specified as dependencies are executed first.
Let’s say that we create anntp role that configures a host to synchronize its time with an NTP server. Ansible allows us to pass parameters to dependent roles, so let’s also assume that we can pass the NTP server as a parameter to that role.
We specify that the web role depends on the ntp role by creating a roles/web/meta/main.yml file and listing ntp as a role, with a parameter:
Whether you want to reuse a role somebody has already written, or you just want to see how someone else solved the problem you’re working on, Ansible Galaxy can help you out. Ansible Galaxy is an open source repository of Ansible roles contributed by the Ansible community. The roles themselves are stored on GitHub.
Chapter 8. Complex Playbooks
This chapter touches on those additional features, which makes it a bit of a grab bag.
Dealing with Badly Behaved Commands
What if we didn’t have a module that could invoke equivalent commands (wasn’t idempotent)? The answer is to use changed_when and failed_when clauses to change how Ansible identifies that a task has changed state or failed.
First, we need to understand the output of this command the first time it’s run, and the output when it’s run the second time.
failed_when: False is to close task fail, so ansible play will continue to execute. We can run several times of the playbook and see different register variable output.
fail statement here is to stop the execution.
Some module may not report changed state even though it did make change in target machine, so we can check if state changed ourselves by using changed_when clause:
1 2 3 4 5 6 7
- name: initialize the database django_manage: command: createdb --noinput --nodata app_path: "{{ proj_path }}" virtualenv: "{{ venv_path }}" register: result changed_when: '"Creating tables" in result.out|default("")'
We use filter here in changed_when since register variable sometimes doesn’t have out field. Alternatively, we could provide a default value for result.out if it doesn’t exist by using the Jinja2 default filter.
Filter
Filters are a feature of the Jinja2 templating engine. Since Ansible uses Jinja2 for evaluating variables, as well as for templates, you can use filters inside {{ braces }} in your playbooks, as well as inside your template files. Using filters resembles using Unix pipes, whereby a variable is piped through a filter. Jinja2 ships with a set of built-in filters. In addition, Ansible ships with its own filters to augment the Jinja2 filters.
If the variable database_host is defined, the braces will evaluate to the value of that variable. If the variable database_host is not defined, the braces will evaluate to the string localhost.
Filters for Registered Variables
Let’s say we want to run a task and print out its output, even if the task fails. However, if the task does fail, we want Ansible to fail for that host after printing the output.
1 2 3 4 5 6 7 8 9
- name: Run myprog command: /opt/myprog register: result ignore_errors: True
- debug: var=result
- debug: msg="Stop running the playbook if myprog failed" failed_when: result|failed
a list of filters you can use on registered variables to check the status:
1 2 3 4 5 6
| Name | Description | |--------- |------------------------------------------------------- | | failed | True if a registered value is a task that failed | | changed | True if a registered value is a task that changed | | success | True if a registered value is a task that succeeded | | skipped | True if a registered value is a task that was skipped |
The basename filter will let us extract the index.html part of the filename from the full path, allowing us to write the playbook without repeating the filename:
Sometimes a piece of configuration data you need lives somewhere else. Maybe it’s in a text file or a .csv file, and you don’t want to just copy the data into an Ansible variable file because now you have to maintain two copies of the same data.
Ansible has a feature called lookups that allows you to read in configuration data from various sources and then use that data in your playbooks and template.
1 2 3 4 5 6 7 8 9 10 11
| Name | Description | |---------- |------------------------------------ | | file | Contents of a file | | password | Randomly generate a password | | pipe | Output of locally executed command | | env | Environment variable | | template | Jinja2 template after evaluation | | csvfile | Entry in a .csv file | | dnstxt | DNS TXT record | | redis_kv | Redis key lookup | | etcd | etcd key lookup |
You can invoke lookups in your playbooks between {{ braces }}, or you can put them in templates.
Note all Ansible lookup plugins execute on the control machine, not the remote host.
file
Let’s say you have a text file on your control machine that contains a public SSH key that you want to copy to a remote server.
1 2
- name: Add my public key as an EC2 key ec2_key: name=mykey key_material="{{ lookup('file', '/Users/lorin/.ssh/id_rsa.pub') }}"
pipe
The pipe lookup invokes an external program on the control machine and evaluates to the program’s output on standard out.
For example, if our playbooks are version controlled using git, and we want to get the SHA-1 value of the most recent git commit, we could use the pipe lookup
1 2
- name: get SHA of most recent commit debug: msg="{{ lookup('pipe', 'git rev-parse HEAD') }}"
1 2 3 4
TASK: [get the sha of the current commit] ************************************* ok: [myserver] => { "msg": "e7748af0f040d58d61de1917980a210df419eae9" }
env
The env lookup retrieves the value of an environment variable set on the control machine.For example, we could use the lookup like this:
1 2
- name: get the current shell debug: msg="{{ lookup('env', 'SHELL') }}"
1 2 3 4
TASK: [get the current shell] ************************************************* ok: [myserver] => { "msg": "/bin/zsh" }
password
The password lookup evaluates to a random password, and it will also write the password to a file specified in the argument. For example, if we want to create a Postgres user named deploy with a random password and write that password to deploy-password.txton the control machine, we can do this:
In the case of csvfile, the first argument is an entry that must appear exactly once in column 0 (the first column, 0-indexed) of the table.
In our example, we want to look in the file named users.csv and locate where the fields are delimited by commas, look up the row where the value in the first column is sue, and return the value in the second column (column 1, indexed by 0). This evaluates to sue@example.org.
etcd
Etcd is a distributed key-value store, commonly used for keeping configuration data and for implementing service discovery. You can use the etcd lookup to retrieve the value of a key.
For example, let’s say that we have an etcd server running on our control machine, and we set the key weather to the value cloudy by doing something like this:
- name: look up value in etcd debug: msg="{{ lookup('etcd', 'weather') }}"
TASK: [look up value in etcd] ************************************************* ok: [localhost] => { "msg": "cloudy" }
By default, the etcd lookup looks for the etcd server at http://127.0.0.1:4001, but you can change this by setting the ANSIBLE_ETCD_URL environment variable before invoking ansible-playbook.
More Complicated Loops
Up until this point, whenever we’ve written a task that iterates over a list of items, we’ve used the with_items clause to specify a list of items. Although this is the most common way to do loops, Ansible supports other mechanisms for iteration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Name | Input | Looping strategy | |-------------------------- |---------------------- |----------------------------------- | | with_items | List | Loop over list elements | | with_lines | Command to execute | Loop over lines in command output | | with_fileglob | Glob | Loop over filenames | | with_first_found | List of paths | First file in input that exists | | with_dict | Dictionary | Loop over dictionary elements | | with_flattened | List of lists | Loop over flattened list | | with_indexed_items | List | Single iteration | | with_nested | List | Nested loop | | with_random_choice | List | Single iteration | | with_sequence | Sequence of integers | Loop over sequence | | with_subelements | List of dictionaries | Nested loop | | with_together | List of lists | Loop over zipped list | | with_inventory_hostnames | Host pattern | Loop over matching hosts |
The official documentation covers these quite thoroughly, so I’ll show examples from just a few of them to give you a sense of how they work.
with_lines
The with_lines looping construct lets you run an arbitrary command on your control machine and iterate over the output, one line at a time. For example, read a file and iterate over its contents line by line.
1 2 3 4 5 6 7
- name: Send out a slack message slack: domain: example.slack.com token: "{{ slack_token }}" msg: "{{ item }} was in the list" with_lines: - cat files/turing.txt
with_fileglob
The with_fileglob construct is useful for iterating over a set of files on the control machine.
For example, iterate over files that end in .pub in the /var/keys directory, as well as a keys directory next to your playbook. It then uses the file lookup plugin to extract the contents of the file, which are passed to the authorized_key module.
1 2 3 4 5
- name: add public keys to account authorized_key: user=deploy key="{{ lookup('file', item) }}" with_fileglob: - /var/keys/*.pub - keys/*.pub
with_dict
The with_dict construct lets you iterate over a dictionary instead of a list. When you use this looping construct, the item loop variable is a dictionary with two fields:
Ansible implements looping constructs as lookup plugins. That means you can alter the form of lookup to perform as a loop:
1 2 3
- name: Add my public key as an EC2 key ec2_key: name=mykey key_material="{{ item }}" with_file: /Users/lorin/.ssh/id_rsa.pub
Here we prefix with_ with file lookup plugin. Typically, you use a lookup plugin as a looping construct only if it returns a list,
Loop Controls
With version 2.1, Ansible provides users with more control over loop handling.
Setting the Variable Name
The loop_var control allows us to give the iteration variable a different name than the default name, item:
1 2 3 4 5 6 7 8 9
- user: name: "{{ user.name }}" with_items: ## list of dict - { name: gil } - { name: sarina } - { name: leanne } loop_control: loop_var: user
Next one is a advanced usage, use include with with_items, we loop over multiple task at once, in current task we include a task called vhosts.yml which will be executed 3 times with different parameters passed in:
1 2 3 4 5 6 7 8
- name: run a set of tasks in one loop include: vhosts.yml with_items: - { domain: www1.example.com } - { domain: www2.example.com } - { domain: www3.example.com } loop_control: loop_var: vhost
The vhosts.yml file that is going to be included may also contain with_items in some tasks. This would produce a conflict, as the default loop_var item is used for both loops at the same time.
To prevent a naming collision, we specify a different name for loop_var in the outer loop.
The include feature allows you to include tasks or even whole playbooks, depending on where you define an include. It is often used in roles to separate or even group tasks and task arguments to each task in the included file.
For example, you can extract different part of tasks, put them into a separate yml file and include it into another task along with common arguments:
Ansible Tags: If you have a large playbook, it may become useful to be able to run only a specific part of it rather than running everything in the playbook. Ansible supports a tags: attribute for this reason.
Dynamic includes
A common pattern in roles is to define tasks specific to a particular operating system into separate task files.
Since version 2.0, Ansible allows us to dynamically include a file by using variable substitution:
1 2
- include: "{{ ansible_os_family }}.yml" static: no
However, there is a drawback to using dynamic includes: ansible-playbook --list-tasks might not list the tasks from a dynamic include if Ansible does not have enough information to populate the variables that determine which file will be included.
You can use ansible-playbook <playbook> --list-tasks to list all the tasks in it.
Role includes
A special include is the include_role clause. In contrast with the role clause, which will use all parts of the role, the include_role not only allows us to selectively choose what parts of a role will be included and used, but also where in the play.
1 2 3
- name: install php include_role: name: php
This will include and run main.yml from the php role, remember a role can have multiple tasks yml files: main.yml and others.
This will include and run install.yml from php role.
Blocks
Much like the include clause, the block clause provides a mechanism for grouping tasks. The block clause allows you to set conditions or arguments for all tasks within a block at once:
Dealing with error scenarios has always been a challenge. Historically, Ansible has been error agnostic in the sense that errors and failures may occur on a host. Ansible’s default error-handling behavior is to take a host out of the play if a task fails and continue as long as there are hosts remaining that haven’t encountered errors.
1 2 3 4 5 6 7 8
- block: - debug: msg="You will see a failed tasks right after this" - command: /bin/false - debug: "You won't see this message" rescue: # Tasks to be executed in case of a failure in block clause - debug: "You only see this message in case of an failure in the block" always: # Tasks to always be executed - debug: "This will be always executed"
If you have some programming experience, the way error handling is implemented may remind you of the try-catch-finally paradigm, and it works much the same way.
Encrypting Sensitive Data with Vault
Ansible provides an alternative solution: instead of keeping the secrets.yml file out of version control, we can commit an encrypted version. That way, even if our version-control repository were compromised, the attacker would not have access to the contents of the secrets.yml file unless he also had the password used for the encryption.
The ansible-vault command-line tool allows you to create and edit an encrypted file that ansible-playbook will recognize and decrypt automatically, given the password.
You will be prompted for a password, and then ansible-vault will launch a text editor so that you can populate the file. It launches the editor specified in the $EDITOR environment variable. If that variable is not defined, it defaults to vim.
If the argument to --vault-password-file has the executable bit set, Ansible will execute it and use the contents of standard out as the vault password. This allows you to use a script to provide the password to Ansible.
1 2 3 4 5 6 7 8
| Command | Description | |-------------------------------- |--------------------------------------------------- | | ansible-vault encrypt file.yml | Encrypt the plain-text file.yml file | | ansible-vault decrypt file.yml | Decrypt the encrypted file.yml file | | ansible-vault view file.yml | Print the contents of the encrypted file.yml file | | ansible-vault create file.yml | Create a new encrypted file.yml file | | ansible-vault edit file.yml | Edit an encrypted file.yml file | | ansible-vault rekey file.yml | Change the password on an encrypted file.yml file |
Chapter 9. Customizing Hosts, Runs, and Handlers
In this chapter, we cover Ansible features that provide customization by controlling which hosts to run against, how tasks are run, and how handlers are run.
Patterns for Specifying Hosts
Instead of specifying a single host or group for a play, you can specify a pattern. You’ve already seen the all pattern, which will run a play against all known hosts:
1
hosts: all
You can specify a union of two groups with a colon. You specify all dev and staging machines as follows:
1
hosts: dev:staging
1 2 3 4 5 6 7 8 9 10
| Action | Example Usage | |--------------------------- |----------------------------- | | All hosts | all | | All hosts | * | | Union | dev:staging | | Intersection | dev:&database | | Exclusion | dev:!queue | | Wildcard | *.example.com | | Range of numbered servers | web[5:12] | | Regular expression | ~web\d+\.example\.(com|org) |
Ansible supports multiple combinations of patterns—for example:
1
hosts: dev:staging:&database:!queue
Limiting Which Hosts Run
Use the -l hosts or --limit hosts flag to tell Ansible to limit the hosts to run the playbook against the specified list of hosts
Sometimes you want to run a particular task on the control machine instead of on the remote host. Ansible provides the local_action clause for tasks to support this. For example, when we check the node ready status in k8s cluster.
Imagine that the server we want to install Mezzanine onto has just booted, so that if we run our playbook too soon, it will error out because the server hasn’t fully started up yet. We could start off our playbook by invoking the wait_for module to wait until the SSH server is ready to accept connections before we execute the rest of the playbook.
1 2
- name: wait for ssh server to be running local_action: wait_for port=22 host="{{ inventory_hostname }}" search_regex=OpenSSH
Note that inventory_hostname evaluates to the name of the remote host, not localhost. That’s because the scope of these variables is still the remote host, even though the task is executing locally.
If your play involves multiple hosts, and you use local_action, the task will be executed multiple times, one for each host. You can restrict this by using run_once
Running a Task on a Machine Other Than the Host
Sometimes you want to run a task that’s associated with a host, but you want to execute the task on a different server. You can use the delegate_to clause to run the task on a different host.
1 2 3 4 5 6
- name: enable alerts for web servers hosts: web tasks: - name: enable alerts nagios: action=enable_alerts service=web host={{ inventory_hostname }} delegate_to: nagios.example.com
In this example, Ansible would execute the nagios task on nagios.example.com, but the inventory_hostname variable referenced in the play would evaluate to the web host.
Note: if you specify delegate_to: localhost to control machine, it’s the same as local_action, also the same as connection: local
Running on One Host at a Time
By default, Ansible runs each task in parallel across all hosts. Sometimes you want to run your task on one host at a time. The canonical example is when upgrading application servers that are behind a load balancer. Typically, you take the application server out of the load balancer, upgrade it, and put it back. But you don’t want to take all of your application servers out of the load balancer, or your service will become unavailable.
You can use the serial clause on a play to tell Ansible to restrict the number of hosts that a play runs on.
- name: upgrade packages on servers behind load balancer hosts: myhosts serial: 1 tasks: - name: get the ec2 instance id and elastic load balancer id ec2_facts:
- name: take the host out of the elastic load balancer local_action: ec2_elb args: instance_id: "{{ ansible_ec2_instance_id }}" state: absent
- name: put the host back in the elastic load balancer local_action: ec2_elb args: instance_id: "{{ ansible_ec2_instance_id }}" state: present ec2_elbs: "{{ item }}" with_items: ec2_elbs
In our example, we pass 1 as the argument to the serial clause, telling Ansible to run on only one host at a time. If we had passed 2, Ansible would have run two hosts at a time.
Normally, when a task fails, Ansible stops running tasks against the host that fails, but continues to run against other hosts. In the load-balancing scenario, you might want Ansible to fail the entire play before all hosts have failed a task. Otherwise, you might end up with the situation where you have taken each host out of the load balancer, and have it fail, leaving no hosts left inside your load balancer.
You can use a max_fail_percentage clause along with the serial clause to specify the maximum percentage of failed hosts before Ansible fails the entire play. For example, assume that we specify a maximum fail percentage of 25%, as shown here:
1 2 3 4 5 6
- name: upgrade packages on servers behind load balancer hosts: myhosts serial: 1 max_fail_percentage: 25 tasks: # tasks go here
If you want Ansible to fail if any of the hosts fail a task, set the max_fail_percentage to 0.
Note: any_errors_fatal: true is just like set max_fail_percentage to 0, with the any_errors_fatal option, any failure on any host in a multi-host play will be treated as fatal and Ansible will exit immediately without waiting for the other hosts.
We can get even more sophisticated. For example, you might want to run the play on one host first, to verify that the play works as expected, and then run the play on a larger number of hosts in subsequent runs.
1 2 3 4 5 6 7
- name: configure CDN servers hosts: cdn serial: - 1 - 30% tasks: # tasks go here
In the preceding play with 30 CDN hosts, on the first batch run Ansible would run against one host, and on each subsequent batch run it would run against at most 30% of the hosts (e.g., 1, 10, 10, 9).
Running Only Once
Using run_once can be particularly useful when using local_action if your playbook involves multiple hosts, and you want to run the local task only once:
1 2 3
- name: run the task locally, only once local_action: command /opt/my-custom-command run_once: true
Running Strategies
The strategy clause on a play level gives you additional control over how Ansible behaves per task for all hosts.
The default behavior we are already familiar with is the linear strategy. This is the strategy in which Ansible executes one task on all hosts and waits until the task has completed (of failed) on all hosts before it executes the next task on all hosts. As a result, a task takes as much time as the slowest host takes to complete the task.
Linear
Note: I forget that host file can define variable, here sleep_seconds can be referred in task:
1 2 3
one sleep_seconds=1 two sleep_seconds=6 three sleep_seconds=10
Note that the orders show up is the complete order in target host, first done at top.
Another strategy available in Ansible is the free strategy. In contrast to linear, Ansible will not wait for results of the task to execute on all hosts. Instead, if a host completes one task, Ansible will execute the next task on that host.
1 2 3 4
- hosts: all strategy: free tasks: ...
Advanced Handlers
When we covered handlers, you learned that they are usually executed after all tasks, once, and only when they get notified. But keep in mind there are not only tasks, but pre_tasks, tasks, and post_tasks.
Each tasks section in a playbook is handled separately; any handler notified in pre_tasks, tasks, or post_tasks is executed at the end of each section. As a result, it is possible to execute one handler several times in one play:
Note: the rest of Chapter 9 is useless for me now, just skip it.
Chapter 16. Debugging Ansible Playbooks
Humane Error Messages
Enable the plugin by adding the following to the defaults section of ansible.cfg:
1 2
[defaults] stdout_callback = debug
the debug callback plugin makes this output much easier for a human to read, for example the format is like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
TASK [check out the repository on the host] ************************************* fatal: [web]: FAILED! => { "changed": false, "cmd": "/usr/bin/git clone --origin origin '' /home/vagrant/mezzanine/mezzani ... }
STDERR:
Cloning into '/home/vagrant/mezzanine/mezzanine_example'... Permission denied (publickey). fatal: Could not read from remote repository. ... MSG:
Cloning into '/home/vagrant/mezzanine/mezzanine_example'... Permission denied (publickey). fatal: Could not read from remote repository. ...
Debugging SSH Issues
Sometimes Ansible fails to make a successful SSH connection with the host. When this happens, it’s helpful to see exactly what arguments Ansible is passing to the underlying SSH client so you can reproduce the problem manually on the command line.
If you invoke ansible-playbook with the -vvv argument, you can see the exact SSH commands that Ansible invokes. This can be handy for debugging.
Note that usually I use -v flag
Sometimes you might need to use -vvvv when debugging a connection issue, in order to see an error message that the SSH client is throwing.
The Debug Module
We’ve used the debug module several times in this book. It’s Ansible’s version of a print statement.
1 2 3
- debug: var=myvariable - debug: msg="The value of myvariable is {{ var }}" - debug: var=hostvars[inventory_hostname]
Playbook Debugger
Ansible 2.1 added support for an interactive debugger. To enable debugging, add strategy: debug to your play; for example:
1 2 3 4
- name: an example play strategy: debug tasks: ...
If debugging is enabled, Ansible drops into the debugger when a task fails, for example, I write a task like this:
TASK [install.components : interactive debug] ************************************************************************ fatal: [myk8s2.fyre.ibm.com]: FAILED! => { "changed": false, "rc": 126, "results": [ "No package matching 'xxx' found available, installed or updated" ] }
MSG:
No package matching 'xxx' found available, installed or updated
Debugger invoked (debug) p task
Let’s see the command list
1 2 3 4 5 6 7 8 9
| Command | Description | |----------------------|---------------------------------------------| | p var | Print out the value of a supported variable | | task.args[key]=value | Modify an argument for the failed task | | vars[key]=value | Modify the value of a variable | | r | rerun the failed task | | c | continue execute next | | q | abort the play | | help | show help message |
variables supported by the debugger
1 2 3 4 5 6 7
| Command | Description | |-------------|----------------------------------------| | p task | the name of the failed task | | p task.args | The module arguments | | p result | The result returned by the failed task | | p vars | Value of all known variables | | p vars[key] | Value of one variable |
The Assert Module
The assert module will fail with an error if a specified condition is not met. For example, to fail the playbook if there’s no eth1 interface:
1 2 3
- name: assert that eth1 interface exists assert: that: ansible_eth1 is defined
When debugging a playbook, it can be helpful to insert assertions so that a failure happens as soon as any assumption you’ve made has been violated.
Keep in mind that the code in an assert statement is Jinja2, not Python.
Checking Your Playbook Before Execution
The ansible-playbook command supports several flags that allow you to sanity check your playbook before you execute it.
Syntax Check
The --syntax-check flag checks that your playbook’s syntax is valid, but it does not execute it.
The -C and --check flags run Ansible in check mode (sometimes known as dry-run), which tells you whether each task in the playbook will modify the host, but does not make any changes to the server.
One of the challenges with using check mode is that later parts of a playbook might succeed only if earlier parts of the playbook were executed.
Diff (Show File Changes)
The -D and -diff flags output differences for any files that are changed on the remote machine. It’s a helpful option to use in conjunction with --check to show how Ansible would change the file if it were run normally:
Sometimes you don’t want Ansible to run every single task in your playbook, particularly when you’re first writing and debugging the playbook. Ansible provides several command-line options that let you control which tasks run.
Step
The --step flag, shown in Example 16-7, has Ansible prompt you before running each task, like this:
The --start-at-task taskname flag tells Ansible to start running the playbook at the specified task, instead of at the beginning. This can be handy if one of your tasks failed because there was a bug in one of your tasks, and you want to rerun your playbook starting at the task you just fixed.
Ansible allows you to add one or more tags to a task or a play. For example, here’s a play that’s tagged with foo and a task that’s tagged with bar and quux:
- name: run arbitrary command command: /opt/myprog tags: - bar - quux
Use the -t tagnames or --tags tagnames flag to tell Ansible to run only plays and tasks that have certain tags. Use the --skip-tags tagnames flag to tell Ansible to skip plays and tasks that have certain tags.
We want to use systemd as cgroup driver for docker and kubelet, let’s see how to achieve that.
First you need to understand what is systemd and cgroup?
You can refer to this article.
systemd is a suite of system management daemons, libraries, and utilities designed as a central management and configuration platform for the GNU/Linux computer operating system. It provides a system and service manager that runs as PID 1 and starts the rest of the system as alternative to the traditional sysVinit.
systemd organizes processes with cgroups, this is a Linux kernel feature to limit, police and account the resource usage of certain processes (actually process groups).
Configure docker
After you install and start docker, by default it will use cgroupfs as the cgroup driver, check by running:
Currently, the kubelet cannot automatically detects the cgroup driver used by the CRI runtime, but the value of --cgroup-driver must match the cgroup driver used by the CRI runtime to ensure the health of the kubelet.
Note: interesting thing is kubeadm init now can automatically detect and set kubelet with the same cgroup driver as docker (I use version 1.13.x).
There is a file: /var/lib/kubelet/kubeadm-flags.env, that kubeadm init and kubeadm join generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically, in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf you can see it:
you will see systemd resides in /var/lib/kubelet/kubeadm-flags.env:
Anyway let’s see how to do the configuration manually. After install kubelet, go to edit /etc/systemd/system/kubelet.service.d/10-kubeadm.conf file, add this line:
Append $KUBELET_CGROUP_ARGS at end of ExecStart=/usr/bin/kubelet statement:
Note: in the file /etc/systemd/system/kubelet.service, it seems you can also configure here: ExecStart=/usr/bin/kubelet --cgroup-driver=systemd, not very clear the difference.
Then when you complete kubeadm init, verify the change:
Just like the blogs I wrote before: Offline Package Installation I and Solve Conflicts in RPM installation, Use rpm or bare yum command to install downloaded rpm file works but I find somehow this will cause some maintenance problems, for example
1 2 3 4 5 6
Warning: RPMDB altered outside of yum. ** Found 51 pre-existing rpmdb problem(s), 'yum check' output follows: bash-4.2.46-31.el7.x86_64 is a duplicate with bash-4.2.46-30.el7.x86_64 binutils-2.27-34.base.el7.x86_64 is a duplicate with binutils-2.27-28.base.el7_5.1.x86_64 coreutils-8.22-23.el7.x86_64 is a duplicate with coreutils-8.22-21.el7.x86_64 cryptsetup-libs-2.0.3-3.el7.x86_64 is a duplicate with cryptsetup-libs-1.7.4-4.el7.x86_64
I need to find a way that can automatically figure out the dependency chain, install the rpm required from download pool.
Create a yum repository
Install createrepo package:
1
yum install -y createrepo
Next, creates the necessary metadata for your Yum repository, as well as the sqlite database for speeding up yum operations. For example, /root/docker directory contains all rpms that install docker needs:
1
createrepo --database /root/docker
you will find it generates a folder called repodata that contains:
To define a new repository, you can either add a [repository] section to the /etc/yum.conf file, or to a .repo file in the /etc/yum.repos.d/ directory. All files with the .repo file extension in this directory are read by yum, and it is recommended to define your repositories here instead of in /etc/yum.conf.
For example, create a docker-local.repo file in /etc/yum.repos.d/ directory, baseurl points to the folder that holds downloaded rpms:
This series contains something that is too short to be a blog, so put them all here, chronologically.
02/25/2019
hostPath in PersistentVolume is the mount path in host machine. mountPath in containers field is the mount path inside the container.
02/27/2019
Docker uses /var/lib/docker to store your images, containers, and local named volumes. Deleting this can result in data loss and possibly stop the engine from running. The overlay2 subdirectory specifically contains the various filesystem layers for images and containers.
Vim readonly mode, can open the same file in multiple windows:
1
vim -R file
02/28/2019
reboot machine rightnow, -r means reboot, for example:
1
shutdown -r now
If you execute remotely, use ssh example.com to test if it bring up.
Jenkins: the exit code of last command of the Jenkin’s Execute Shell build step is what determines the success/failure, now it’s better to wrap the code snippet as a script and execute it. Need to do more search on it.
03/02/2019
For ls command: If no operands are given, the contents of the current directory are displayed. If more than one operand is given, non-directory operands are displayed first; directory and non-directory operands are sorted separately and in lexicographical order.
I use this feature with tail command to pick latest package, for example:
1
ls | grep ansible-* | tail -1
ansible has log_path setting in ~/.ansible.cfg file, for example:
when run systemctl start docker, these directories are created: /var/lib/docker, /run/docker, etc/docker.
03/05/2019
Find Red Hat or CentOS version:
1 2 3
cat /etc/os-release
Red Hat Enterprise Linux Server release 7.6 (Maipo)
03/13/2019
I see people sometimes use /bin/cp, /bin/rm in script, why they don’t use cp or rm directly? The answer is cp or rm may be an alias in target machine! For example:
1 2 3
alias cp='cp -i' alias mv='mv -i' alias rm='rm -i'
So when use cp -f source target it will still prompt you the overwrite confirm if target and source are the same. /bin/cp -f source target is correct way to go.
03/16/2019
These are from Ansible: Up and Running, 2nd Edition book:
A staging environment (stage) is a nearly exact replica of a production environment for software testing. Staging environments are made to test codes, builds, and updates to ensure quality under a production-like environment before application deployment.
03/17/2019
These are from Ansible: Up and Running, 2nd Edition book:
Mezzanine: similar in spirit to WordPress. Mezzanine is built on top of Django, the free Python-based framework for writing web applications.
Fabric: a Python-based tool that helps automate running tasks via SSH.
SQLite is serveless database: Most SQL database engines are implemented as a separate server process. Programs that want to access the database communicate with the server using some kind of interprocess communication (typically TCP/IP) to send requests to the server and to receive back results. SQLite does not work this way. With SQLite, the process that wants to access the database reads and writes directly from the database files on disk. There is no intermediary server process.
03/18/2019
Today after Fyre maintenance, one of my VM cannot resolve hostname, when I run
1
ping google.com
it hangs, also nslookup doesn’t work as well.
Let’s check /etc/resolv.conf file, it is good:
ELECTRON: Build cross platform desktop apps with JavaScript, HTML, and CSS
03/27/2019
This is from a issue I encountered: when we setup a NFS server as storage in K8s cluster with the /etc/exports file, we need to restrict the clients who is able to access the NFS mount instead of something like:
1
/data *(rw,insecure,async,no_root_squash)
Correct way is to specify which NFS client can access:
for host in {{ lookup('env','nfsclienthosts') }}; do echo "{{ dfsDataDir }} "$host"(rw,insecure,async,no_root_squash)" >> /etc/exports done
I want to say be careful with the space in exports file, from RHEL NFS exports
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Important
The format of the /etc/exports file is very precise, particularly in regards to use of the space character. Remember to always separate exported file systems from hosts and hosts from one another with a space character. However, there should be no other space characters in the file except on comment lines.
For example, the following two lines do not mean the same thing:
The first line allows only users from bob.example.com read and write access to the /home directory. The second line allows users from bob.example.com to mount the directory as read-only (the default), while the rest of the world can mount it read/write.
03/28/2019
ps aux command will not show full outputs and the lines are truncated, if you are in a lightweight Linux distributions like BusyBox, you can try:
1
ps aux | cat
otherwise try:
1 2
ps auxw ps auxww
03/29/2019
open terminal run vimtutor, haha.
03/30/2019
see memory usage
1
free -h
clean swap space
1
swapoff -a && swapon -a
Nagios: open source Industry Standard In IT Infrastructure Monitoring
HAProxy:The Reliable, High Performance TCP/HTTP Load Balancer
/bin/false is a system command that is used anytime you need to pass a command to a program that should do nothing more than exit with an error. It’s the companion to /bin/true. Both of these are very old and standard POSIX utilities and neither produce any output by definition. true is sometimes used for a shell script that should loop indefinitely, like:
1 2 3 4 5 6 7 8
while true; do ... # Waste time if [ $wasted_time -gt 100000 ]; then exit 0 fi ... done
/usr/sbin/nologin is specifically designed to replace a shell and produces output complaining you can’t log-in. Before it existed, it was common to use /bin/false for dummy users, but could be confusing since the user doesn’t know why they’re kicked off.
04/01/2019
Sometimes when I login to a user home, the prompt is like:
1
bash-4.2$
instead of
1
[demo@myk8s1 ~]$
the reason is .bash_history .bash_logout .bash_profile .bashrc under /home/demo are missing!
04/02/2019
docker bind mounts and volume, in our application, we use mount type in docker run command:
global user start file: /etc/bashrc, the umask is inside it.
Note that umask uses subtraction.
tools which preserve permissions apply the appropriate mode and ignore umask: cp -p, tar -p.
04/24/2019
I find sometimes I use grep -r XXX . cannot find the pattern in files in current and subdirectories. The reason is -r flag will not process symbolic link except it’s on the command link. you can use:
1
grep -Rn XXX .
-R will follow symbolic links
-n will show line number for each matched result
-i make it case-insensitive
-F used looking for fixed string to save time
1
grep -Rn --include "*.txt" XXX .
if you know the pattern of the file, you can specify that using --include, you can also mention using --exclude option.
workaround when you cannot find rpm or package to install in linux, download the binary and put it in working PATH, for example, to use jq, download binaries from here. Add executable bit and move to /usr/bin.
07/01/2019
check directory current used size:
1 2 3
# -s: total # -h: human du -sh <path to directory>
if you want to know the partition size associated with this directory, for example /var/lib/docker, use
1
df -h /var
07/02/2019
change file or directory time stamp to 1969-12-31 16:00
1
touch -a -m -t 196912311600 xx.txt
-a: change the access time of a file. By default it will take the current system time and update the atime field.
-m: change the modification time of a file.
-t: explicitly specify the time
reference and update the time stamp of file a.txt from the time stamp of b.txt file
1
touch a.txt -r b.txt
Change time stamp recursively
1
find . -type f -exec touch -a -m -t 196912311600 {} +
Convert format from DOS to UNIX:
If you open a file via vim and see there are many ^M:
1 2 3 4 5
NOTICE^M ^M This document includes License Information documents below for multiple Programs. Each License Information document identifies the Program(s) to which it applies. Only those License Information documents for the Program(s) for which Licensee has acquired entitlements apply.^M ^M ^M
This is because it’s DOS format:
1 2 3
file LA_en.ORIG
LA_en.ORIG: ASCII text, with very long lines, with CRLF, LF line terminators
how to convert to UNIX?
1 2
yum install -y dos2unix dos2unix <file name>
07/12/2019
Previously I use
1
ls -ltr --block-size=M
to see the human readable size for each file, actually use
1
ls -ltrh
is enough!
07/15/2019
new tech word linting: the process of running a program that will analyse code for potential errors. For example, PHPLint, JSLint.
07/24/2019
if run script using sudo, for example:
1
sudo script.sh
then every command in scipt is executed with sudo.
08/05/2019
find file owned by a particular user or group
1
find <path> -user <dsadm> -group <dstage>
find file by case-insensitive name and use -ls format
1
find <path> -iname <name> -type f -ls
find particular files and change the chown or chmod
explain:
chmod 755 {} \; specifies the command that will be executed by find for each file. {} is replaced by the file path, and the semicolon(;) denotes the end of the command (escaped, otherwise it would be interpreted by the shell instead of find).
08/09/2019
if var is not set, use default value 123456:
1
var=${var:-"123456"}
08/28/2019
docker commit will not apply chmod 777 / in new image, the permission mode of / directory is still original. Not sure why.
09/05/2019
curl can be used to verbose request in detail, to check the RESTful API content.
Today I spend some time to investigate how to remove nodes from the k8s cluster that built by kubeadm.
For example, I have a 3 nodes cluster called k8stest, I deploy the application in namespacetest-1, each worker node (k8stest2 and k8stest3) holds some pods:
You can use kubectl drain to safely evict all of your pods from a node before you perform maintenance on the node (e.g. kernel upgrade, hardware maintenance, etc.). Safe evictions allow the pod’s containers to gracefully terminate and will respect the PodDisruptionBudgets you have specified.
The drain evicts or deletes all pods except mirror pods (which cannot be deleted through the API server). If there are DaemonSet-managed pods, drain will not proceed without --ignore-daemonsets, and regardless it will not delete any DaemonSet-managed pods, because those pods would be immediately replaced by the DaemonSet controller, which ignores unschedulable markings. If there are any pods that are neither mirror pods nor managed by ReplicationController, ReplicaSet, DaemonSet, StatefulSet or Job, then drain will not delete any pods unless you use --force. --force will also allow deletion to proceed if the managing resource of one or more pods is missing.
When kubectl drain returns successfully, that indicates that all of the pods (except the ones excluded as described in the previous paragraph) have been safely evicted (respecting the desired graceful termination period, and without violating any application-level disruption SLOs). It is then safe to bring down the node by powering down its physical machine or, if running on a cloud platform, deleting its virtual machine.
Let’s ssh to k8stest2 node and see what happens here, the payloads were gone:
1 2 3 4 5 6 7 8 9 10
ssh k8stest2.fyre.ibm.com docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0fbbb64d93d0 fa6f35a1c14d "/install-cni.sh" 6 hours ago Up 6 hours k8s_install-cni_calico-node-txjpn_kube-system_4b916269-3d49-11e9-b6b3-00163e01eecc_0 b78013d4f454 427a0694c75c "start_runit" 6 hours ago Up 6 hours k8s_calico-node_calico-node-txjpn_kube-system_4b916269-3d49-11e9-b6b3-00163e01eecc_0 c6aaf7cbf713 01cfa56edcfc "/usr/local/bin/kube..." 6 hours ago Up 6 hours k8s_kube-proxy_kube-proxy-52njn_kube-system_4b944a11-3d49-11e9-b6b3-00163e01eecc_0 542bc4662ee4 k8s.gcr.io/pause:3.1 "/pause" 6 hours ago Up 6 hours k8s_POD_calico-node-txjpn_kube-system_4b916269-3d49-11e9-b6b3-00163e01eecc_0 86ee508f0aa1 k8s.gcr.io/pause:3.1 "/pause" 6 hours ago Up 6 hours k8s_POD_kube-proxy-52njn_kube-system_4b944a11-3d49-11e9-b6b3-00163e01eecc_0
The given node will be marked unschedulable to prevent new pods from arriving.
1 2 3 4 5 6
kubectl get nodes
NAME STATUS ROLES AGE VERSION k8stest1.fyre.ibm.com Ready master 6h11m v1.13.2 k8stest2.fyre.ibm.com Ready,SchedulingDisabled <none> 5h57m v1.13.2 k8stest3.fyre.ibm.com Ready <none> 5h57m v1.13.2
Because the dedicated node k8stest2 was drained, so is-servicesdocker and is-xmetadocker keep pending:
I want to offline install some rpms for an application, I put all dependencies for that application in a dedicated directory. The problem is it will cause conflicts with the old installed ones, I also want to keep old existing rpms because they may needed by other packages. For example, I offline install bind-utils use command:
1
yum --disablerepo=* install -y ./bind-utils/*.rpm
Error output:
1 2 3 4 5 6 7 8 9 10 11
... Error: Package: 1:openssl-1.0.2k-12.el7.x86_64 (@anaconda/7.5) Requires: openssl-libs(x86-64) = 1:1.0.2k-12.el7 Removing: 1:openssl-libs-1.0.2k-12.el7.x86_64 (@anaconda/7.5) openssl-libs(x86-64) = 1:1.0.2k-12.el7 Updated By: 1:openssl-libs-1.0.2k-16.el7.x86_64 (/openssl-libs-1.0.2k-16.el7.x86_64) openssl-libs(x86-64) = 1:1.0.2k-16.el7 ... You could try using --skip-broken to work around the problem ** Found 1 pre-existing rpmdb problem(s), 'yum check' output follows: mokutil-15-1.el7.x86_64 is a duplicate with mokutil-12-1.el7.x86_64
This error shows that yum try to update old rpm with new one but this breaks the dependency chain. Option --skip-broken won’t work here, it will skip the dependency-problem rpm which include exactly what I need:
1 2
# skipped bind-utils.x86_64 32:9.9.4-73.el7_6
Then I try to use:
1
rpm -ivh ./bind-utils/*.rpm
still bad with conflicts:
1 2 3
... file /usr/lib64/openssl/engines/libcapi.so from install of openssl-libs-1:1.0.2k-16.el7.x86_64 conflicts with file from package openssl-libs-1:1.0.2k-12.el7.x86_64 ...
Solution
After doing research I find some rpm options may help:
This upgrades or installs the package currently installed to a newer version. This is the same as install, except all other version(s) of the package are removed after the new package is installed.
This will upgrade packages, but only ones for which an earlier version is installed. ... --force Same as using --replacepkgs, --replacefiles, and --oldpackage. --replacepkgs Install the packages even if some of them are already installed on this system. --replacefiles Install the packages even if they replace files from other, already installed, packages. --oldpackage Allow an upgrade to replace a newer package with an older one.
Let’s add --force flag and try again, this works and the old rpms are still there: