docs: add 100 Days of DevOps challenge notes
This commit is contained in:
303
100 - days of devops/devops-1-10.md
Normal file
303
100 - days of devops/devops-1-10.md
Normal file
@@ -0,0 +1,303 @@
|
||||
## Task 1
|
||||
|
||||
To accommodate the backup agent tool's specifications, the system admin team at xFusionCorp Industries requires the creation of a user with a non-interactive shell. Here's your task:
|
||||
|
||||
Create a user named siva with a non-interactive shell on App Server 2.
|
||||
|
||||
```bash
|
||||
sudo useradd -s /sbin/nologin siva
|
||||
|
||||
# Verify
|
||||
grep siva /etc/passwd
|
||||
```
|
||||
|
||||
|
||||
## Task 2
|
||||
|
||||
As part of the temporary assignment to the Nautilus project, a developer named siva requires access for a limited duration. To ensure smooth access management, a temporary user account with an expiry date is needed. Here's what you need to do:
|
||||
|
||||
Create a user named siva on App Server 1 in Stratos Datacenter. Set the expiry date to 2027-04-15, ensuring the user is created in lowercase as per standard protocol.
|
||||
|
||||
```bash
|
||||
sudo useradd -e 2027-04-15 siva
|
||||
|
||||
# Verify
|
||||
sudo chage -l siva | grep -i expire
|
||||
```
|
||||
## Task 3
|
||||
|
||||
Your task is to disable direct SSH root login on all app servers within the Stratos Datacenter.
|
||||
|
||||
App servers are these:
|
||||
Server Name IP Hostname User Password Purpose
|
||||
Application Server 1 Dynamic stapp01 tony Ir0nM@n Hosts Nautilus Application 1
|
||||
Application Server 2 Dynamic stapp02 steve Am3ric@ Hosts Nautilus Application 2
|
||||
Application Server 3 Dynamic stapp03 banner BigGr33n Hosts Nautilus Application 3
|
||||
|
||||
```bash
|
||||
cat > harden_ssh.sh <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CREDS="${1:-creds.txt}"
|
||||
|
||||
REMOTE_CMD='
|
||||
echo "$SUDO_PASS" | sudo -S -p "" sed -i "s/^#*\s*PermitRootLogin.*/PermitRootLogin no/" /etc/ssh/sshd_config &&
|
||||
echo "$SUDO_PASS" | sudo -S -p "" sshd -t &&
|
||||
echo "$SUDO_PASS" | sudo -S -p "" systemctl restart sshd &&
|
||||
echo "--- $(hostname) effective config ---" &&
|
||||
echo "$SUDO_PASS" | sudo -S -p "" sshd -T | grep -i permitrootlogin
|
||||
'
|
||||
|
||||
while read -r host user pass <&3; do
|
||||
[[ -z "$host" || "$host" == \#* ]] && continue
|
||||
echo "==> Hardening $host"
|
||||
SSHPASS="$pass" sshpass -e ssh -n \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR \
|
||||
"$user@$host" \
|
||||
"SUDO_PASS='$pass' bash -c '$REMOTE_CMD'"
|
||||
echo
|
||||
done 3< "$CREDS"
|
||||
SCRIPT
|
||||
chmod +x harden_ssh.sh
|
||||
|
||||
# --- deps ---
|
||||
sudo yum install -y sshpass 2>/dev/null || sudo apt install -y sshpass
|
||||
./harden_ssh.sh
|
||||
```
|
||||
|
||||
|
||||
## Task 4
|
||||
|
||||
Your task is to grant executable permissions to the /tmp/xfusioncorp.sh script on App Server 1. Additionally, ensure that all users have the capability to execute it.
|
||||
|
||||
```bash
|
||||
chmod a+rx /tmp/xfusioncorp.sh
|
||||
```
|
||||
|
||||
## Task 5
|
||||
|
||||
Following a security audit, the xFusionCorp Industries security team has opted to enhance application and server security with SELinux. To initiate testing, the following requirements have been established for App server 2 in the Stratos Datacenter:
|
||||
|
||||
Install the required SELinux packages.
|
||||
|
||||
Permanently disable SELinux for the time being; it will be re-enabled after necessary configuration changes.
|
||||
|
||||
No need to reboot the server, as a scheduled maintenance reboot is already planned for tonight.
|
||||
|
||||
Disregard the current status of SELinux via the command line; the final status after the reboot should be disabled.
|
||||
|
||||
```bash
|
||||
sudo yum install -y selinux-policy selinux-policy-targeted policycoreutils policycoreutils-python-utils libselinux-utils setools-console mcstrans
|
||||
sudo sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
|
||||
|
||||
# verify
|
||||
grep '^SELINUX=' /etc/selinux/config
|
||||
|
||||
```
|
||||
|
||||
## Task 6
|
||||
|
||||
The Nautilus system admins team has prepared scripts to automate several day-to-day tasks. They want them to be deployed on all app servers in Stratos DC on a set schedule. Before that they need to test similar functionality with a sample cron job. Therefore, perform the steps below:
|
||||
|
||||
a. Install cronie package on all Nautilus app servers and start crond service.
|
||||
b. Add a cron */5 * * * * echo hello > /tmp/cron_text for root user
|
||||
|
||||
```bash
|
||||
# --- creds ---
|
||||
cat > creds.txt <<'EOF'
|
||||
stapp01 tony Ir0nM@n
|
||||
stapp02 steve Am3ric@
|
||||
stapp03 banner BigGr33n
|
||||
EOF
|
||||
chmod 600 creds.txt
|
||||
|
||||
# --- deploy script ---
|
||||
cat > deploy_cron.sh <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CREDS="${1:-creds.txt}"
|
||||
|
||||
# Everything runs inside one root shell: authenticate sudo ONCE via -S,
|
||||
# then stdin is free for the crontab pipe inside.
|
||||
REMOTE_CMD='
|
||||
yum install -y cronie
|
||||
systemctl enable --now crond
|
||||
echo "*/5 * * * * echo hello > /tmp/cron_text" | crontab -u root -
|
||||
echo "--- $(hostname) ---"
|
||||
systemctl is-active crond
|
||||
crontab -u root -l
|
||||
'
|
||||
|
||||
while read -r host user pass <&3; do
|
||||
[[ -z "$host" || "$host" == \#* ]] && continue
|
||||
echo "==> Deploying cron on $host"
|
||||
SSHPASS="$pass" sshpass -e ssh -n \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR \
|
||||
"$user@$host" \
|
||||
"echo '$pass' | sudo -S -p '' bash -c '$REMOTE_CMD'" \
|
||||
2> >(grep -v '^\[sudo\]' >&2)
|
||||
echo
|
||||
done 3< "$CREDS"
|
||||
SCRIPT
|
||||
chmod +x deploy_cron.sh
|
||||
|
||||
# --- run ---
|
||||
./deploy_cron.sh
|
||||
```
|
||||
|
||||
## Task 7
|
||||
|
||||
The system admins team of xFusionCorp Industries has set up some scripts on jump host that run on regular intervals and perform operations on all app servers in Stratos Datacenter. To make these scripts work properly we need to make sure the thor user on jump host has password-less SSH access to all app servers through their respective sudo users (i.e tony for app server 1). Based on the requirements, perform the following:
|
||||
|
||||
Set up a password-less authentication from user thor on jump host to all app servers through their respective sudo users.
|
||||
|
||||
```bash
|
||||
ssh-keygen -t rsa -b 4096 -N '' -f ~/.ssh/id_rsa
|
||||
|
||||
# --- creds ---
|
||||
cat > creds.txt <<'EOF'
|
||||
stapp01 tony Ir0nM@n
|
||||
stapp02 steve Am3ric@
|
||||
stapp03 banner BigGr33n
|
||||
EOF
|
||||
chmod 600 creds.txt
|
||||
|
||||
# --- copy keys ---
|
||||
cat > setup_keys.sh <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CREDS="${1:-creds.txt}"
|
||||
|
||||
while read -r host user pass <&3; do
|
||||
[[ -z "$host" || "$host" == \#* ]] && continue
|
||||
echo "==> Copying key to $host"
|
||||
SSHPASS="$pass" sshpass -e ssh-copy-id \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
"$user@$host"
|
||||
done 3< "$CREDS"
|
||||
SCRIPT
|
||||
chmod +x setup_keys.sh
|
||||
./setup_keys.sh
|
||||
```
|
||||
|
||||
## Task 8
|
||||
|
||||
During the weekly meeting, the Nautilus DevOps team discussed about the automation and configuration management solutions that they want to implement. While considering several options, the team has decided to go with Ansible for now due to its simple setup and minimal pre-requisites. The team wanted to start testing using Ansible, so they have decided to use jump host as an Ansible controller to test different kind of tasks on rest of the servers.
|
||||
|
||||
Install ansible version 4.10.0 on Jump host using pip3 only. Make sure Ansible binary is available globally on this system, i.e all users on this system are able to run Ansible commands.
|
||||
|
||||
```bash
|
||||
sudo pip3 install ansible==4.10.0
|
||||
|
||||
# verify
|
||||
ansible --version
|
||||
which ansible # want /usr/local/bin/ansible
|
||||
```
|
||||
|
||||
## Task 9
|
||||
|
||||
There is a critical issue going on with the Nautilus application in Stratos DC. The production support team identified that the application is unable to connect to the database. After digging into the issue, the team found that mariadb service is down on the database server.
|
||||
|
||||
```bash
|
||||
systemctl enable mariadb
|
||||
|
||||
sudo chown mysql:mysql /run/mariadb
|
||||
sudo chmod 755 /run/mariadb
|
||||
sudo systemctl start mariadb
|
||||
sudo systemctl status mariadb --no-pager
|
||||
|
||||
systemctl start mariadb
|
||||
```
|
||||
|
||||
## Task 10
|
||||
|
||||
The production support team of xFusionCorp Industries is working on developing some bash scripts to automate different day to day tasks. One is to create a bash script for archiving website content files. They have a static website running on App Server 1 in Stratos Datacenter, and they need to create a bash script named beta_archive.sh which should accomplish the following tasks. (Also remember to place the script under the /scripts directory on App Server 1).
|
||||
|
||||
|
||||
|
||||
a. Create a zip archive named xfusioncorp_beta.zip of /var/www/html/beta directory.
|
||||
|
||||
|
||||
b. Save the archive in the /archives/ directory on the App Server 1. This is a temporary storage, as archives from this location will be cleaned on a weekly basis. Therefore, the archive should also be copied to the Nautilus Storage Server so it can be retrieved later for validation purposes.
|
||||
|
||||
|
||||
c. Copy the created archive to the Nautilus Storage Server server in the /archives/ location.
|
||||
|
||||
|
||||
d. Please make sure script won't ask for password while copying the archive file. Additionally, the respective server user (for example, tony in case of App Server 1) must be able to run it.
|
||||
|
||||
|
||||
e. Do not use sudo inside the script.
|
||||
|
||||
Note:
|
||||
The zip package must be installed on given App Server before executing the script. This package is essential for creating the zip archive of the website files. Install it manually outside the script.
|
||||
|
||||
```bash
|
||||
# install zip (task explicitly says do this manually)
|
||||
sudo yum install -y zip
|
||||
|
||||
# make dirs, owned by tony so script needs no sudo
|
||||
sudo mkdir -p /scripts /archives
|
||||
sudo chown tony:tony /scripts /archives
|
||||
|
||||
# --- on stapp01, as tony ---
|
||||
|
||||
# generate tony's key if he doesn't have one
|
||||
[ -f ~/.ssh/id_rsa ] || ssh-keygen -t rsa -b 4096 -N '' -f ~/.ssh/id_rsa
|
||||
|
||||
# creds for the storage server (the only target for this task)
|
||||
cat > /tmp/creds.txt <<'EOF'
|
||||
ststor01 natasha Bl@kW
|
||||
EOF
|
||||
chmod 600 /tmp/creds.txt
|
||||
|
||||
# distribute tony's pubkey to storage server
|
||||
cat > /tmp/setup_keys.sh <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
CREDS="${1:-/tmp/creds.txt}"
|
||||
while read -r host user pass <&3; do
|
||||
[[ -z "$host" || "$host" == \#* ]] && continue
|
||||
echo "==> Copying tony's key to $host"
|
||||
SSHPASS="$pass" sshpass -e ssh-copy-id \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
"$user@$host"
|
||||
done 3< "$CREDS"
|
||||
SCRIPT
|
||||
chmod +x /tmp/setup_keys.sh
|
||||
/tmp/setup_keys.sh
|
||||
|
||||
# verify passwordless works
|
||||
ssh -o BatchMode=yes -o StrictHostKeyChecking=no natasha@ststor01 hostname
|
||||
|
||||
# archive copying script
|
||||
cat > /scripts/beta_archive.sh <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SRC="/var/www/html/beta"
|
||||
ARCHIVE_NAME="xfusioncorp_beta.zip"
|
||||
LOCAL_DIR="/archives"
|
||||
REMOTE_USER="natasha"
|
||||
REMOTE_HOST="ststor01"
|
||||
REMOTE_DIR="/archives"
|
||||
|
||||
# a + b: create zip in local /archives
|
||||
zip -r "${LOCAL_DIR}/${ARCHIVE_NAME}" "$SRC"
|
||||
|
||||
# c: copy to storage server (passwordless via key)
|
||||
scp "${LOCAL_DIR}/${ARCHIVE_NAME}" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"
|
||||
|
||||
echo "Archive created and copied: ${ARCHIVE_NAME}"
|
||||
EOF
|
||||
chmod +x /scripts/beta_archive.sh
|
||||
```
|
||||
636
100 - days of devops/devops-11-20.md
Normal file
636
100 - days of devops/devops-11-20.md
Normal file
@@ -0,0 +1,636 @@
|
||||
## Task 11
|
||||
|
||||
The Nautilus application development team recently finished the beta version of one of their Java-based applications, which they are planning to deploy on one of the app servers in Stratos DC. After an internal team meeting, they have decided to use the tomcat application server. Based on the requirements mentioned below complete the task:
|
||||
|
||||
a. Install tomcat server on App Server 3.
|
||||
|
||||
b. Configure it to run on port 8088.
|
||||
|
||||
c. There is a ROOT.war file on Jump host at location /tmp.
|
||||
|
||||
```bash
|
||||
# from jump-host
|
||||
scp /tmp/ROOT.war steve@stapp02:/tmp/
|
||||
|
||||
sudo yum install -y tomcat tomcat-webapps tomcat-admin-webapps
|
||||
|
||||
sudo sed -i 's/port="8080"/port="6300"/' /etc/tomcat/server.xml
|
||||
# verify
|
||||
sudo grep -n '6300' /etc/tomcat/server.xml
|
||||
|
||||
sudo cp /tmp/ROOT.war /var/lib/tomcat/webapps/
|
||||
sudo chown tomcat:tomcat /var/lib/tomcat/webapps/ROOT.war
|
||||
|
||||
sudo systemctl enable --now tomcat
|
||||
sudo systemctl restart tomcat # ensure it picks up server.xml change if already running
|
||||
|
||||
# give it a few seconds to explode the WAR, then:
|
||||
curl -I http://localhost:8088/
|
||||
|
||||
# possible problem
|
||||
# Stock ROOT collision. If tomcat-webapps installed its own ROOT/ dir, and your ROOT.war sits beside it, tomcat may not redeploy over an # existing exploded dir. Nuke the stock one first: sudo rm -rf /var/lib/tomcat/webapps/ROOT then drop the war and restart.
|
||||
```
|
||||
|
||||
## Task 12
|
||||
|
||||
Our monitoring tool has reported an issue in Stratos Datacenter. One of our app servers has an issue, as its Apache service is not reachable on port 8089 (which is the Apache port). The service itself could be down, the firewall could be at fault, or something else could be causing the issue.
|
||||
|
||||
Use tools like telnet, netstat, etc. to find and fix the issue. Also make sure Apache is reachable from the jump host without compromising any security settings.
|
||||
|
||||
Once fixed, you can test the same using command curl http://stapp02:8089 command from jump host.
|
||||
|
||||
```bash
|
||||
ss -tulnp
|
||||
# kill semndmail sitting on the port
|
||||
# start httpd
|
||||
# remove reject rule from iptables
|
||||
iptables -D INPUT 5
|
||||
```
|
||||
|
||||
## Task 13
|
||||
|
||||
We have one of our websites up and running on our Nautilus infrastructure in Stratos DC. Our security team has raised a concern that right now Apache’s port i.e 8084 is open for all since there is no firewall installed on these hosts. So we have decided to add some security layer for these hosts and after discussions and recommendations we have come up with the following requirements:
|
||||
|
||||
1. Install iptables and all its dependencies on each app host.
|
||||
2. Block incoming port 8084 on all apps for everyone except for LBR host.
|
||||
3. Make sure the rules remain, even after system reboot.
|
||||
|
||||
```bash
|
||||
# --- resolve LBR host IP (script runs from jump-host) ---
|
||||
LBR_IP=$(getent hosts stlb01 | awk '{print $1}')
|
||||
echo "LBR IP resolved to: $LBR_IP"
|
||||
[ -z "$LBR_IP" ] && { echo "ERROR: could not resolve stlb01"; exit 1; }
|
||||
|
||||
# --- creds (app servers only — the three targets) ---
|
||||
cat > creds.txt <<'EOF'
|
||||
stapp01 tony Ir0nM@n
|
||||
stapp02 steve Am3ric@
|
||||
stapp03 banner BigGr33n
|
||||
EOF
|
||||
chmod 600 creds.txt
|
||||
|
||||
# --- firewall script ---
|
||||
cat > fw_8084.sh <<SCRIPT
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CREDS="\${1:-creds.txt}"
|
||||
LBR_IP="10.244.196.7"
|
||||
|
||||
REMOTE_CMD="
|
||||
yum install -y iptables iptables-services
|
||||
# iptables -F
|
||||
iptables -A INPUT -p tcp --dport 8084 -s \$LBR_IP -j ACCEPT
|
||||
iptables -A INPUT -p tcp --dport 8084 -j DROP
|
||||
systemctl enable --now iptables
|
||||
iptables-save > /etc/sysconfig/iptables
|
||||
echo \"--- \\\$(hostname) 8084 rules ---\"
|
||||
iptables -nvL INPUT | grep 8084
|
||||
"
|
||||
|
||||
while read -r host user pass <&3; do
|
||||
[[ -z "\$host" || "\$host" == \\#* ]] && continue
|
||||
echo "==> Firewalling \$host"
|
||||
SSHPASS="\$pass" sshpass -e ssh -n \\
|
||||
-o StrictHostKeyChecking=no \\
|
||||
-o UserKnownHostsFile=/dev/null \\
|
||||
-o LogLevel=ERROR \\
|
||||
"\$user@\$host" \\
|
||||
"echo '\$pass' | sudo -S -p '' bash -c '\$REMOTE_CMD'" \\
|
||||
2> >(grep -v '^\[sudo\]' >&2)
|
||||
echo
|
||||
done 3< "\$CREDS"
|
||||
SCRIPT
|
||||
chmod +x fw_8084.sh
|
||||
|
||||
# --- run ---
|
||||
export LBR_IP=10.244.196.7
|
||||
./fw_8084.sh
|
||||
```
|
||||
|
||||
## Task 14
|
||||
|
||||
The production support team of xFusionCorp Industries has deployed some of the latest monitoring tools to keep an eye on every service, application, etc. running on the systems. One of the monitoring systems reported about Apache service unavailability on one of the app servers in Stratos DC.
|
||||
|
||||
Identify the faulty app host and fix the issue. Make sure Apache service is up and running on all app hosts. They might not have hosted any code yet on these servers, so you don't need to worry if Apache isn't serving any pages. Just make sure the service is up and running. Also, make sure Apache is running on port 6400 on all app servers.
|
||||
|
||||
|
||||
```bash
|
||||
# --- creds ---
|
||||
cat > creds.txt <<'EOF'
|
||||
stapp01 tony Ir0nM@n
|
||||
stapp02 steve Am3ric@
|
||||
stapp03 banner BigGr33n
|
||||
EOF
|
||||
chmod 600 creds.txt
|
||||
|
||||
# --- RECON: what's the state of httpd + port 6400 on each box ---
|
||||
cat > apache_recon.sh <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CREDS="${1:-creds.txt}"
|
||||
|
||||
REMOTE_CMD='
|
||||
echo "active: $(systemctl is-active httpd 2>/dev/null)"
|
||||
echo "enabled: $(systemctl is-enabled httpd 2>/dev/null)"
|
||||
echo "Listen directive: $(grep -iE "^Listen" /etc/httpd/conf/httpd.conf 2>/dev/null)"
|
||||
echo "bound on 6400: $(ss -tlnp | grep :6400 || echo NO)"
|
||||
echo "last httpd error:"
|
||||
journalctl -u httpd --no-pager 2>/dev/null | tail -5
|
||||
'
|
||||
|
||||
while read -r host user pass <&3; do
|
||||
[[ -z "$host" || "$host" == \#* ]] && continue
|
||||
echo "===================== $host ====================="
|
||||
SSHPASS="$pass" sshpass -e ssh -n \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR \
|
||||
"$user@$host" \
|
||||
"echo '$pass' | sudo -S -p '' bash -c '$REMOTE_CMD'" \
|
||||
2> >(grep -v '^\[sudo\]' >&2)
|
||||
echo
|
||||
done 3< "$CREDS"
|
||||
SCRIPT
|
||||
chmod +x apache_recon.sh
|
||||
./apache_recon.sh
|
||||
|
||||
|
||||
# Verify
|
||||
cat > apache_verify.sh <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail # note: no -e, we want to check ALL hosts even if one fails
|
||||
|
||||
CREDS="${1:-creds.txt}"
|
||||
|
||||
REMOTE_CMD='
|
||||
fail=0
|
||||
# 1. service running?
|
||||
if [ "$(systemctl is-active httpd 2>/dev/null)" = "active" ]; then
|
||||
echo " [PASS] httpd is active"
|
||||
else
|
||||
echo " [FAIL] httpd NOT active (state: $(systemctl is-active httpd 2>/dev/null))"
|
||||
fail=1
|
||||
fi
|
||||
# 2. enabled for boot?
|
||||
if [ "$(systemctl is-enabled httpd 2>/dev/null)" = "enabled" ]; then
|
||||
echo " [PASS] httpd enabled at boot"
|
||||
else
|
||||
echo " [WARN] httpd NOT enabled at boot (state: $(systemctl is-enabled httpd 2>/dev/null))"
|
||||
fi
|
||||
# 3. bound on 6400?
|
||||
if ss -tlnp 2>/dev/null | grep -q :6400; then
|
||||
echo " [PASS] listening on port 6400"
|
||||
else
|
||||
echo " [FAIL] NOT listening on 6400"
|
||||
fail=1
|
||||
fi
|
||||
# 4. actually responds? (any HTTP code = apache answering; 403/404 fine, task says no content needed)
|
||||
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 http://localhost:6400/ 2>/dev/null)
|
||||
if [ -n "$code" ] && [ "$code" != "000" ]; then
|
||||
echo " [PASS] HTTP response on 6400 (code: $code)"
|
||||
else
|
||||
echo " [FAIL] no HTTP response on 6400"
|
||||
fail=1
|
||||
fi
|
||||
echo " RESULT: $([ $fail -eq 0 ] && echo ALL-GOOD || echo NEEDS-ATTENTION)"
|
||||
'
|
||||
|
||||
overall=0
|
||||
while read -r host user pass <&3; do
|
||||
[[ -z "$host" || "$host" == \#* ]] && continue
|
||||
echo "===================== $host ====================="
|
||||
out=$(SSHPASS="$pass" sshpass -e ssh -n \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR \
|
||||
"$user@$host" \
|
||||
"echo '$pass' | sudo -S -p '' bash -c '$REMOTE_CMD'" \
|
||||
2> >(grep -v '^\[sudo\]' >&2))
|
||||
echo "$out"
|
||||
echo "$out" | grep -q NEEDS-ATTENTION && overall=1
|
||||
echo
|
||||
done 3< "$CREDS"
|
||||
|
||||
echo "=================================================="
|
||||
[ $overall -eq 0 ] && echo "✅ ALL HOSTS PASS" || echo "❌ ONE OR MORE HOSTS NEED ATTENTION"
|
||||
SCRIPT
|
||||
chmod +x apache_verify.sh
|
||||
./apache_verify.sh
|
||||
```
|
||||
|
||||
## Task 15
|
||||
|
||||
The system admins team of xFusionCorp Industries needs to deploy a new application on App Server 2 in Stratos Datacenter. They have some pre-requites to get ready that server for application deployment. Prepare the server as per requirements shared below:
|
||||
|
||||
1. Install and configure nginx on App Server 2.
|
||||
2. On App Server 2 there is a self signed SSL certificate and key present at location /tmp/nautilus.crt and /tmp/nautilus.key. Move them to some appropriate location and deploy the same in Nginx.
|
||||
3. Create an index.html file with content Welcome! under Nginx document root.
|
||||
4. For final testing try to access the App Server 2 link (via hostname) from jump host using curl command. For example: curl -Ik https://<app-server-name>/.
|
||||
|
||||
|
||||
```bash
|
||||
# ===== nginx + SSL deploy on stapp02 (run as steve) =====
|
||||
|
||||
# 1. install nginx
|
||||
sudo yum install -y nginx
|
||||
|
||||
# 2. relocate certs out of /tmp to a proper location + lock down perms
|
||||
sudo mkdir -p /etc/nginx/ssl
|
||||
sudo mv /tmp/nautilus.crt /etc/nginx/ssl/nautilus.crt
|
||||
sudo mv /tmp/nautilus.key /etc/nginx/ssl/nautilus.key
|
||||
sudo chmod 600 /etc/nginx/ssl/nautilus.key
|
||||
sudo chmod 644 /etc/nginx/ssl/nautilus.crt
|
||||
|
||||
# 3. SSL server block — server_name pulled from the box's own FQDN
|
||||
FQDN=$(hostname -f)
|
||||
echo "Configuring nginx for: $FQDN"
|
||||
sudo tee /etc/nginx/conf.d/nautilus.conf > /dev/null <<EOF
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name ${FQDN};
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/nautilus.crt;
|
||||
ssl_certificate_key /etc/nginx/ssl/nautilus.key;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ =404;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 4. index.html with Welcome! under docroot
|
||||
echo 'Welcome!' | sudo tee /usr/share/nginx/html/index.html > /dev/null
|
||||
|
||||
# 5. validate + start + enable
|
||||
sudo nginx -t
|
||||
sudo systemctl enable --now nginx
|
||||
sudo systemctl restart nginx
|
||||
|
||||
# 6. local sanity check (before testing from jump-host)
|
||||
echo "--- local curl test ---"
|
||||
curl -Ik https://localhost/
|
||||
echo "--- service state ---"
|
||||
sudo systemctl is-active nginx
|
||||
```
|
||||
|
||||
## Task 16
|
||||
|
||||
Day by day traffic is increasing on one of the websites managed by the Nautilus production support team. Therefore, the team has observed a degradation in website performance. Following discussions about this issue, the team has decided to deploy this application on a high availability stack i.e on Nautilus infra in Stratos DC. They started the migration last month and it is almost done, as only the LBR server configuration is pending. Configure LBR server as per the information given below:
|
||||
|
||||
a. Install nginx on the LBR (load balancer) server if it is not already installed.
|
||||
b. Configure load-balancing with the http context making use of all App Servers. Ensure that you update only the main Nginx configuration file located at /etc/nginx/nginx.conf.
|
||||
c. Make sure you do not update the apache port that is already defined in the apache configuration on all app servers, also make sure apache service is up and running on all the app servers.
|
||||
d. Once done, you can access the website by running curl http://stlb01:80 in the terminal.
|
||||
|
||||
|
||||
```bash
|
||||
# ===== LBR config — run from jump-host =====
|
||||
|
||||
# --- creds (3 app servers for recon + LBR for deploy) ---
|
||||
# ===== LBR config — run from jump-host =====
|
||||
|
||||
# --- creds (3 app servers for recon + LBR for deploy) ---
|
||||
cat > creds.txt <<'EOF'
|
||||
stapp01 tony Ir0nM@n
|
||||
stapp02 steve Am3ric@
|
||||
stapp03 banner BigGr33n
|
||||
stlb01 loki Mischi3f
|
||||
EOF
|
||||
chmod 600 creds.txt
|
||||
|
||||
# helper: pull pass for a given host from creds
|
||||
getpass() { awk -v h="$1" '$1==h{print $3}' creds.txt; }
|
||||
getuser() { awk -v h="$1" '$1==h{print $2}' creds.txt; }
|
||||
|
||||
# --- STEP 1: recon — confirm apache up on all app servers + grab the port ---
|
||||
echo "===== Apache recon on app servers ====="
|
||||
for host in stapp01 stapp02 stapp03; do
|
||||
u=$(getuser "$host"); p=$(getpass "$host")
|
||||
echo "=== $host ==="
|
||||
SSHPASS="$p" sshpass -e ssh -n \
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \
|
||||
"$u@$host" \
|
||||
"echo '$p' | sudo -S -p '' bash -c 'systemctl is-active httpd; grep -i \"^Listen\" /etc/httpd/conf/httpd.conf'" \
|
||||
2> >(grep -v '^\[sudo\]' >&2)
|
||||
done
|
||||
|
||||
# --- STEP 2: auto-detect the apache port from stapp01 ---
|
||||
u=$(getuser stapp01); p=$(getpass stapp01)
|
||||
APACHE_PORT=$(SSHPASS="$p" sshpass -e ssh -n \
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \
|
||||
"$u@stapp01" \
|
||||
"grep -iE '^Listen' /etc/httpd/conf/httpd.conf | awk '{print \$2}' | tr -d '\r'" \
|
||||
2>/dev/null)
|
||||
echo ">>> Detected Apache port: $APACHE_PORT"
|
||||
[ -z "$APACHE_PORT" ] && { echo "ERROR: could not detect apache port"; exit 1; }
|
||||
|
||||
# --- STEP 3: build nginx.conf LOCALLY (quoted heredoc keeps nginx $vars literal) ---
|
||||
cat > /tmp/lbr_nginx.conf <<'EOF'
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log;
|
||||
pid /run/nginx.pid;
|
||||
|
||||
include /usr/share/nginx/modules/*.conf;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 4096;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
upstream app_servers {
|
||||
server stapp01:__PORT__;
|
||||
server stapp02:__PORT__;
|
||||
server stapp03:__PORT__;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name stlb01;
|
||||
|
||||
location / {
|
||||
proxy_pass http://app_servers;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# substitute the real apache port
|
||||
sed -i "s/__PORT__/${APACHE_PORT}/g" /tmp/lbr_nginx.conf
|
||||
echo ">>> nginx.conf built with upstream port ${APACHE_PORT}:"
|
||||
grep -A4 'upstream app_servers' /tmp/lbr_nginx.conf
|
||||
|
||||
# --- STEP 4: ship config to stlb01 + deploy ---
|
||||
u=$(getuser stlb01); p=$(getpass stlb01)
|
||||
|
||||
# scp the config to a temp spot on stlb01 (passwordless via sshpass)
|
||||
SSHPASS="$p" sshpass -e scp \
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \
|
||||
/tmp/lbr_nginx.conf "$u@stlb01:/tmp/lbr_nginx.conf"
|
||||
|
||||
# install nginx, back up original, move new config in, validate, start
|
||||
SSHPASS="$p" sshpass -e ssh -n \
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \
|
||||
"$u@stlb01" \
|
||||
"echo '$p' | sudo -S -p '' bash -c '
|
||||
yum install -y nginx
|
||||
cp -n /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak 2>/dev/null || true
|
||||
cp /tmp/lbr_nginx.conf /etc/nginx/nginx.conf
|
||||
nginx -t
|
||||
systemctl enable --now nginx
|
||||
systemctl restart nginx
|
||||
echo \"--- nginx state ---\"
|
||||
systemctl is-active nginx
|
||||
'" \
|
||||
2> >(grep -v '^\[sudo\]' >&2)
|
||||
|
||||
# --- STEP 5: final test ---
|
||||
echo "===== TEST: curl http://stlb01:80 ====="
|
||||
u=$(getuser stlb01); p=$(getpass stlb01)
|
||||
SSHPASS="$p" sshpass -e ssh -n \
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \
|
||||
"$u@stlb01" "curl -s http://stlb01:80 | head -20" \
|
||||
2>/dev/null
|
||||
|
||||
echo
|
||||
echo "You can also test directly from jump-host:"
|
||||
echo " curl http://stlb01:80"
|
||||
```
|
||||
|
||||
## Task 17
|
||||
|
||||
The Nautilus application development team has shared that they are planning to deploy one newly developed application on Nautilus infra in Stratos DC. The application uses PostgreSQL database, so as a pre-requisite we need to set up PostgreSQL database server as per requirements shared below:
|
||||
|
||||
PostgreSQL database server is already installed on the Nautilus database server.
|
||||
a. Create a database user kodekloud_aim and set its password to 8FmzjvFU6S.
|
||||
b. Create a database kodekloud_db8 and grant full permissions to user kodekloud_aim on this database.
|
||||
|
||||
```bash
|
||||
# ===== PostgreSQL setup on stdb01 (run as peter) =====
|
||||
|
||||
# sudo into postgres user first
|
||||
sudo -i -u postgres
|
||||
|
||||
psql -c "SELECT version();"
|
||||
psql -c "CREATE USER kodekloud_aim WITH PASSWORD '8FmzjvFU6S';"
|
||||
psql -c "CREATE DATABASE kodekloud_db8;"
|
||||
psql -c "GRANT ALL PRIVILEGES ON DATABASE kodekloud_db8 TO kodekloud_aim;"
|
||||
psql -c "\l" | grep kodekloud_db8
|
||||
psql -c "\du" | grep kodekloud_aim
|
||||
psql -c "\l kodekloud_db8"
|
||||
```
|
||||
|
||||
## Task 18
|
||||
|
||||
We need to setup a database server on Nautilus DB Server in Stratos Datacenter. Please perform the below given steps on DB Server:
|
||||
|
||||
a. Install/Configure MariaDB server.
|
||||
b. Create a database named kodekloud_db3.
|
||||
c. Create a user called kodekloud_gem and set its password to YchZHRcLkL.
|
||||
d. Grant full permissions to user kodekloud_gem on database kodekloud_db3.
|
||||
|
||||
```bash
|
||||
# ===== MariaDB setup on stdb01 (run as peter) =====
|
||||
|
||||
# a. install + start MariaDB
|
||||
sudo yum install -y mariadb-server
|
||||
sudo systemctl enable --now mariadb
|
||||
|
||||
# b/c/d. create db, user, grant privileges (root via unix_socket auth)
|
||||
sudo mysql -e "CREATE DATABASE kodekloud_db3;"
|
||||
sudo mysql -e "CREATE USER 'kodekloud_gem'@'localhost' IDENTIFIED BY 'YchZHRcLkL';"
|
||||
sudo mysql -e "GRANT ALL PRIVILEGES ON kodekloud_db3.* TO 'kodekloud_gem'@'localhost';"
|
||||
sudo mysql -e "FLUSH PRIVILEGES;"
|
||||
|
||||
# --- verify ---
|
||||
echo "--- database exists? ---"
|
||||
sudo mysql -e "SHOW DATABASES;" | grep kodekloud_db3
|
||||
echo "--- user exists? ---"
|
||||
sudo mysql -e "SELECT User,Host FROM mysql.user WHERE User='kodekloud_gem';"
|
||||
echo "--- grants ---"
|
||||
sudo mysql -e "SHOW GRANTS FOR 'kodekloud_gem'@'localhost';"
|
||||
```
|
||||
|
||||
## Task 18
|
||||
|
||||
xFusionCorp Industries is planning to host two static websites on their infra in Stratos Datacenter. The development of these websites is still in-progress, but we want to get the servers ready. Please perform the following steps to accomplish the task:
|
||||
|
||||
a. Install httpd package and dependencies on app server 2.
|
||||
b. Apache should serve on port 5000.
|
||||
c. There are two website's backups /home/thor/media and /home/thor/demo on jump_host. Set them up on Apache in a way that media should work on the link http://localhost:5000/media/ and demo should work on link http://localhost:5000/demo/ on the mentioned app server.
|
||||
d. Once configured you should be able to access the website using curl command on the respective app server, i.e curl http://localhost:5000/media/ and curl http://localhost:5000/demo/
|
||||
|
||||
```bash
|
||||
# ===== Apache two-site setup: jump-host → stapp02 (via sshpass) =====
|
||||
|
||||
# --- creds ---
|
||||
cat > creds.txt <<'EOF'
|
||||
stapp02 steve Am3ric@
|
||||
EOF
|
||||
chmod 600 creds.txt
|
||||
|
||||
# pull steve's creds
|
||||
USER=$(awk '$1=="stapp02"{print $2}' creds.txt)
|
||||
PASS=$(awk '$1=="stapp02"{print $3}' creds.txt)
|
||||
|
||||
SSHOPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
|
||||
|
||||
# --- Step 1: ship both backups from jump-host to stapp02 ---
|
||||
echo "==> Transferring media + demo to stapp02"
|
||||
SSHPASS="$PASS" sshpass -e scp $SSHOPTS -r /home/thor/media "$USER@stapp02:/tmp/"
|
||||
SSHPASS="$PASS" sshpass -e scp $SSHOPTS -r /home/thor/demo "$USER@stapp02:/tmp/"
|
||||
|
||||
# --- Step 2: install + configure on stapp02 (single sudo shell) ---
|
||||
echo "==> Configuring httpd on stapp02"
|
||||
REMOTE_CMD='
|
||||
yum install -y httpd
|
||||
sed -i "s/^Listen 80$/Listen 5000/" /etc/httpd/conf/httpd.conf
|
||||
mkdir -p /var/www/html/media /var/www/html/demo
|
||||
cp -r /tmp/media/* /var/www/html/media/
|
||||
cp -r /tmp/demo/* /var/www/html/demo/
|
||||
apachectl configtest
|
||||
systemctl enable --now httpd
|
||||
systemctl restart httpd
|
||||
echo "--- Listen directive ---"
|
||||
grep -i "^Listen" /etc/httpd/conf/httpd.conf
|
||||
echo "--- httpd state ---"
|
||||
systemctl is-active httpd
|
||||
'
|
||||
SSHPASS="$PASS" sshpass -e ssh -n $SSHOPTS "$USER@stapp02" \
|
||||
"echo '$PASS' | sudo -S -p '' bash -c '$REMOTE_CMD'" \
|
||||
2> >(grep -v '^\[sudo\]' >&2)
|
||||
|
||||
# --- Step 3: test on stapp02 (curl runs locally on the app server) ---
|
||||
echo "===== TEST: /media/ ====="
|
||||
SSHPASS="$PASS" sshpass -e ssh -n $SSHOPTS "$USER@stapp02" \
|
||||
'curl -s http://localhost:5000/media/ | head'
|
||||
```
|
||||
|
||||
## Task 20
|
||||
|
||||
The Nautilus application development team is planning to launch a new PHP-based application, which they want to deploy on Nautilus infra in Stratos DC. The development team had a meeting with the production support team and they have shared some requirements regarding the infrastructure. Below are the requirements they shared:
|
||||
|
||||
a. Install nginx on app server 2 , configure it to use port 8098 and its document root should be /var/www/html.
|
||||
b. Install php-fpm version 8.1 on app server 2, it must use the unix socket /var/run/php-fpm/default.sock (create the parent directories if don't exist).
|
||||
c. Configure php-fpm and nginx to work together.
|
||||
d. Once configured correctly, you can test the website using curl http://stapp02:8098/index.php command from jump host.
|
||||
|
||||
NOTE: We have copied two files, index.php and info.php, under /var/www/html as part of the PHP-based application setup. Please do not modify these files.
|
||||
|
||||
```bash
|
||||
# ===== nginx + php-fpm 8.2 setup: jump-host → stapp02 (via sshpass) =====
|
||||
|
||||
# --- creds ---
|
||||
cat > creds.txt <<'EOF'
|
||||
stapp02 steve Am3ric@
|
||||
EOF
|
||||
chmod 600 creds.txt
|
||||
|
||||
USER=$(awk '$1=="stapp02"{print $2}' creds.txt)
|
||||
PASS=$(awk '$1=="stapp02"{print $3}' creds.txt)
|
||||
SSHOPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
|
||||
|
||||
# --- build nginx server block LOCALLY (quoted heredoc = nginx $vars stay literal) ---
|
||||
cat > /tmp/php_site.conf <<'EOF'
|
||||
server {
|
||||
listen 8092;
|
||||
server_name stapp02.stratos.xfusioncorp.com;
|
||||
root /var/www/html;
|
||||
index index.php index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php-fpm/default.sock;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# --- ship the nginx config to stapp02 ---
|
||||
echo "==> Shipping nginx config"
|
||||
SSHPASS="$PASS" sshpass -e scp $SSHOPTS /tmp/php_site.conf "$USER@stapp02:/tmp/php_site.conf"
|
||||
|
||||
# --- remote: install + configure everything in one sudo shell ---
|
||||
echo "==> Configuring stapp02"
|
||||
REMOTE_CMD='
|
||||
set -e
|
||||
|
||||
# a. nginx
|
||||
yum install -y nginx
|
||||
|
||||
# b. php-fpm 8.2 — enable the module stream then install
|
||||
yum module reset php -y 2>/dev/null || true
|
||||
yum module enable php:8.2 -y 2>/dev/null || true
|
||||
yum install -y php-fpm
|
||||
echo "--- php-fpm version ---"
|
||||
php-fpm --version | head -1
|
||||
|
||||
# install nginx server block (replace stock default.conf)
|
||||
cp /tmp/php_site.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# php-fpm pool -> unix socket + nginx ownership
|
||||
sed -i "s#^listen = .*#listen = /var/run/php-fpm/default.sock#" /etc/php-fpm.d/www.conf
|
||||
sed -i "s/^;*listen.owner = .*/listen.owner = nginx/" /etc/php-fpm.d/www.conf
|
||||
sed -i "s/^;*listen.group = .*/listen.group = nginx/" /etc/php-fpm.d/www.conf
|
||||
sed -i "s/^;*listen.mode = .*/listen.mode = 0660/" /etc/php-fpm.d/www.conf
|
||||
sed -i "s/^user = .*/user = nginx/" /etc/php-fpm.d/www.conf
|
||||
sed -i "s/^group = .*/group = nginx/" /etc/php-fpm.d/www.conf
|
||||
|
||||
# create socket parent dir (task requirement)
|
||||
mkdir -p /var/run/php-fpm
|
||||
chown nginx:nginx /var/run/php-fpm
|
||||
|
||||
# validate nginx config
|
||||
nginx -t
|
||||
|
||||
# start + enable both
|
||||
systemctl enable --now php-fpm nginx
|
||||
systemctl restart php-fpm nginx
|
||||
|
||||
echo "--- states ---"
|
||||
systemctl is-active php-fpm
|
||||
systemctl is-active nginx
|
||||
echo "--- socket ---"
|
||||
ls -l /var/run/php-fpm/default.sock
|
||||
'
|
||||
|
||||
SSHPASS="$PASS" sshpass -e ssh -n $SSHOPTS "$USER@stapp02" \
|
||||
"echo '$PASS' | sudo -S -p '' bash -c '$REMOTE_CMD'" \
|
||||
2> >(grep -v '^\[sudo\]' >&2)
|
||||
|
||||
# --- test from jump-host ---
|
||||
echo "===== TEST: curl http://stapp02:8092/index.php ====="
|
||||
SSHPASS="$PASS" sshpass -e ssh -n $SSHOPTS "$USER@stapp02" \
|
||||
'curl -s http://localhost:8092/index.php | head -20' 2>/dev/null
|
||||
echo
|
||||
echo "Direct from jump-host: curl http://stapp02:8092/index.php"
|
||||
|
||||
```
|
||||
165
100 - days of devops/devops-21-30.md
Normal file
165
100 - days of devops/devops-21-30.md
Normal file
@@ -0,0 +1,165 @@
|
||||
## Task 21
|
||||
|
||||
The Nautilus development team has provided requirements to the DevOps team for a new application development project, specifically requesting the establishment of a Git repository. Follow the instructions below to create the Git repository on the Storage server in the Stratos DC:
|
||||
|
||||
Utilize yum to install the git package on the Storage Server.
|
||||
Create a bare repository named /opt/beta.git (ensure exact name usage).
|
||||
|
||||
```bash
|
||||
|
||||
ssh natasha@ststor01
|
||||
|
||||
# install git
|
||||
sudo yum install -y git
|
||||
|
||||
# create the bare repo (exact path/name)
|
||||
sudo git init --bare /opt/beta.git
|
||||
|
||||
# verify
|
||||
ls -la /opt/beta.git
|
||||
```
|
||||
|
||||
## Task 22
|
||||
|
||||
The DevOps team established a new Git repository last week, which remains unused at present. However, the Nautilus application development team now requires a copy of this repository on the Storage Server in the Stratos DC. Follow the provided details to clone the repository:
|
||||
|
||||
The repository to be cloned is located at /opt/cluster.git
|
||||
Clone this Git repository to the /usr/src/kodekloudrepos directory. Perform this task using the natasha user, and ensure that no modifications are made to the repository or existing directories, such as changing permissions or making unauthorized alterations.
|
||||
|
||||
```bash
|
||||
|
||||
ssh natasha@ststor01
|
||||
|
||||
git clone /opt/news.git /usr/src/kodekloudrepos
|
||||
ls -la /usr/src/kodekloudrepos
|
||||
|
||||
```
|
||||
|
||||
## Task 23
|
||||
|
||||
There is a Git server utilized by the Nautilus project teams. Recently, a new developer named Jon joined the team and needs to begin working on a project. To begin, he must fork an existing Git repository. Follow the steps below:
|
||||
|
||||
Click on the Gitea UI button located on the top bar to access the Gitea page.
|
||||
Login to Gitea server using username jon and password Jon_pass123.
|
||||
Once logged in, locate the Git repository named sarah/story-blog and fork it under the jon user.
|
||||
|
||||
Note: For tasks requiring web UI changes, screenshots are necessary for review purposes. Additionally, consider utilizing screen recording software such as loom.com to record and share your task completion process.
|
||||
|
||||
Solution:
|
||||
Clicky-clack in gitea UI
|
||||
|
||||
## Task 24
|
||||
|
||||
Nautilus developers are actively working on one of the project repositories, /usr/src/kodekloudrepos/media. Recently, they decided to implement some new features in the application, and they want to maintain those new changes in a separate branch. Below are the requirements that have been shared with the DevOps team:
|
||||
|
||||
On Storage server in Stratos DC create a new branch xfusioncorp_media from master branch in /usr/src/kodekloudrepos/media git repo.
|
||||
|
||||
Please do not try to make any changes in the code.
|
||||
|
||||
```bash
|
||||
ssh natasha@ststor01
|
||||
|
||||
cd /usr/src/kodekloudrepos/media
|
||||
git checkout master
|
||||
git checkout -b xfusioncorp_media
|
||||
```
|
||||
|
||||
## Task 25
|
||||
|
||||
The Nautilus application development team has been working on a project repository /opt/media.git. This repo is cloned at /usr/src/kodekloudrepos on storage server in Stratos DC. They recently shared the following requirements with DevOps team:
|
||||
|
||||
Create a new branch datacenter in /usr/src/kodekloudrepos/media repo from master and copy the /tmp/index.html file (present on storage server itself) into the repo. Further, add/commit this file in the new branch and merge back that branch into master branch. Finally, push the changes to the origin for both of the branches.
|
||||
|
||||
```bash
|
||||
ssh natasha@ststor01
|
||||
|
||||
# do some pretty uninteresant git operations and file copies
|
||||
```
|
||||
|
||||
## Task 26
|
||||
|
||||
The xFusionCorp development team added updates to the project that is maintained under /opt/demo.git repo and cloned under /usr/src/kodekloudrepos/demo. Recently some changes were made on Git server that is hosted on Storage server in Stratos DC. The DevOps team added some new Git remotes, so we need to update remote on /usr/src/kodekloudrepos/demo repository as per details mentioned below:
|
||||
|
||||
a. In /usr/src/kodekloudrepos/demo repo add a new remote dev_demo and point it to /opt/xfusioncorp_demo.git repository.
|
||||
b. There is a file /tmp/index.html on same server; copy this file to the repo and add/commit to master branch.
|
||||
c. Finally push master branch to this new remote origin.
|
||||
|
||||
```bash
|
||||
ssh natasha@ststor01
|
||||
sudo su -
|
||||
|
||||
cd /usr/src/kodekloudrepos/demo
|
||||
git remote add dev_demo /opt/xfusioncorp_demo.git
|
||||
|
||||
# b. copy file in, stage, commit on master
|
||||
git checkout master # make sure we're on master
|
||||
cp /tmp/index.html .
|
||||
git add index.html
|
||||
git commit -m "Add index.html"
|
||||
|
||||
# c. push master to the NEW remote (dev_demo), not origin
|
||||
git push dev_demo master
|
||||
```
|
||||
|
||||
## Task 27
|
||||
|
||||
The Nautilus application development team was working on a git repository /usr/src/kodekloudrepos/games present on Storage server in Stratos DC. However, they reported an issue with the recent commits being pushed to this repo. They have asked the DevOps team to revert repo HEAD to last commit. Below are more details about the task:
|
||||
|
||||
In /usr/src/kodekloudrepos/games git repository, revert the latest commit ( HEAD ) to the previous commit (JFYI the previous commit hash should be with initial commit message ).
|
||||
|
||||
Use revert games message (please use all small letters for commit message) for the new revert commit.
|
||||
|
||||
```bash
|
||||
ssh natasha@ststor01
|
||||
sudo su -
|
||||
|
||||
cd /usr/src/kodekloudrepos/games
|
||||
sudo git log --oneline
|
||||
|
||||
git revert HEAD --no-commit
|
||||
git commit -m "revert games"
|
||||
```
|
||||
|
||||
## Task 28
|
||||
|
||||
The Nautilus application development team has been working on a project repository /opt/news.git. This repo is cloned at /usr/src/kodekloudrepos on storage server in Stratos DC. They recently shared the following requirements with the DevOps team:
|
||||
|
||||
There are two branches in this repository, master and feature. One of the developers is working on the feature branch and their work is still in progress, however they want to merge one of the commits from the feature branch to the master branch, the message for the commit that needs to be merged into master is Update info.txt. Accomplish this task for them, also remember to push your changes eventually.
|
||||
|
||||
```bash
|
||||
ssh natasha@ststor01
|
||||
sudo su -
|
||||
|
||||
cd /usr/src/kodekloudrepos/news
|
||||
git log feature --oneline
|
||||
|
||||
git checkout master
|
||||
git cherry-pick 9472ff2
|
||||
git push origin master
|
||||
```
|
||||
|
||||
|
||||
## Task 29
|
||||
|
||||
Clicky clack in gitea UI
|
||||
|
||||
## Task 30
|
||||
|
||||
The Nautilus application development team was working on a git repository /usr/src/kodekloudrepos/beta present on Storage server in Stratos DC. This was just a test repository and one of the developers just pushed a couple of changes for testing, but now they want to clean this repository along with the commit history/work tree, so they want to point back the HEAD and the branch itself to a commit with message add data.txt file. Find below more details:
|
||||
|
||||
In /usr/src/kodekloudrepos/beta git repository, reset the git commit history so that there are only two commits in the commit history i.e initial commit and add data.txt file.
|
||||
|
||||
Also make sure to push your changes.
|
||||
|
||||
```bash
|
||||
ssh natasha@ststor01
|
||||
sudo su -
|
||||
|
||||
cd /usr/src/kodekloudrepos/beta
|
||||
git log --oneline
|
||||
|
||||
git reset --hard e23457b
|
||||
|
||||
git push -f
|
||||
|
||||
```
|
||||
221
100 - days of devops/devops-31-40.md
Normal file
221
100 - days of devops/devops-31-40.md
Normal file
@@ -0,0 +1,221 @@
|
||||
## Task 31
|
||||
|
||||
The Nautilus application development team was working on a git repository /usr/src/kodekloudrepos/media present on Storage server in Stratos DC. One of the developers stashed some in-progress changes in this repository, but now they want to restore some of the stashed changes. Find below more details to accomplish this task:
|
||||
|
||||
Look for the stashed changes under /usr/src/kodekloudrepos/media git repository, and restore the stash with stash@{1} identifier. Further, commit and push your changes to the origin.
|
||||
|
||||
```bash
|
||||
ssh natasha@ststor01
|
||||
sudo su -
|
||||
|
||||
cd /usr/src/kodekloudrepos/media
|
||||
git stash list
|
||||
|
||||
# git stash apply stash@{1}
|
||||
git stash pop stash@{1}
|
||||
|
||||
sudo git add -A
|
||||
sudo git commit -m "Restore stashed changes from stash@{1}"
|
||||
sudo git push origin master
|
||||
```
|
||||
|
||||
## Task 32
|
||||
|
||||
The Nautilus application development team has been working on a project repository /opt/demo.git. This repo is cloned at /usr/src/kodekloudrepos on storage server in Stratos DC. They recently shared the following requirements with DevOps team:
|
||||
|
||||
One of the developers is working on feature branch and their work is still in progress, however there are some changes which have been pushed into the master branch, the developer now wants to rebase the feature branch with the master branch without loosing any data from the feature branch, also they don't want to add any merge commit by simply merging the master branch into the feature branch. Accomplish this task as per requirements mentioned.
|
||||
|
||||
Also remember to push your changes once done.
|
||||
|
||||
```bash
|
||||
ssh natasha@ststor01
|
||||
sudo su -
|
||||
|
||||
cd /usr/src/kodekloudrepos/demo/
|
||||
|
||||
git checkout feature
|
||||
git rebase master # replay feature on top of master, linear
|
||||
git push origin feature --force # force-push rewritten feature
|
||||
git log --oneline --graph --all
|
||||
```
|
||||
|
||||
|
||||
## Task 33
|
||||
|
||||
Sarah and Max were working on writting some stories which they have pushed to the repository. Max has recently added some new changes and is trying to push them to the repository but he is facing some issues. Below you can find more details:
|
||||
|
||||
SSH into storage server using user max and password Max_pass123. Under /home/max you will find the story-blog repository. Try to push the changes to the origin repo and fix the issues. The story-index.txt must have titles for all 4 stories. Additionally, there is a typo in The Lion and the Mooose line where Mooose should be Mouse.
|
||||
|
||||
Click on the Gitea UI button on the top bar. You should be able to access the Gitea page. You can login to Gitea server from UI using username sarah and password Sarah_pass123 or username max and password Max_pass123.
|
||||
|
||||
Note: For these kind of scenarios requiring changes to be done in a web UI, please take screenshots so that you can share it with us for review in case your task is marked incomplete. You may also consider using a screen recording software such as loom.com to record and share your work.
|
||||
|
||||
```bash
|
||||
ssh max@ststor01
|
||||
# password: Max_pass123
|
||||
cd /home/max/story-blog
|
||||
|
||||
git status
|
||||
git log --oneline -5
|
||||
git push origin master
|
||||
|
||||
```
|
||||
|
||||
## Task 34
|
||||
|
||||
The Nautilus application development team was working on a git repository /opt/cluster.git which is cloned under /usr/src/kodekloudrepos directory present on Storage server in Stratos DC. The team want to setup a hook on this repository, please find below more details:
|
||||
|
||||
Merge the feature branch into the master branch, but before pushing your changes complete below point.
|
||||
|
||||
Create a post-update hook in this git repository so that whenever any changes are pushed to the master branch, it creates a release tag with name release-2023-06-15, where 2023-06-15 is supposed to be the current date. For example if today is 20th June, 2023 then the release tag must be release-2023-06-20. Make sure you test the hook at least once and create a release tag for today's release.
|
||||
|
||||
Finally remember to push your changes.
|
||||
Note: Perform this task using the natasha user, and ensure the repository or existing directory permissions are not altered.
|
||||
|
||||
```bash
|
||||
ssh natasha@ststor01
|
||||
sudo su -
|
||||
|
||||
cd /usr/src/kodekloudrepos/cluster
|
||||
git checkout master
|
||||
git merge feature
|
||||
|
||||
sudo tee /opt/cluster.git/hooks/post-update > /dev/null <<'EOF'
|
||||
#!/bin/bash
|
||||
# post-update hook: tag a dated release when master is pushed
|
||||
release_date=$(date +%F) # YYYY-MM-DD
|
||||
tag="release-${release_date}"
|
||||
git tag -f "$tag" master
|
||||
EOF
|
||||
|
||||
sudo chmod +x /opt/cluster.git/hooks/post-update
|
||||
|
||||
# now post-update hook should be alive
|
||||
git push origin master
|
||||
|
||||
# Verify
|
||||
|
||||
# tag should now exist in the bare repo
|
||||
sudo git --git-dir=/opt/cluster.git tag -l
|
||||
# or from the working clone after fetching
|
||||
sudo git fetch origin --tags
|
||||
sudo git tag -l
|
||||
```
|
||||
|
||||
## Task 35
|
||||
|
||||
The Nautilus DevOps team aims to containerize various applications following a recent meeting with the application development team. They intend to conduct testing with the following steps:
|
||||
|
||||
Install docker-ce and docker compose packages on App Server 3.
|
||||
Initiate the docker service.
|
||||
|
||||
|
||||
```bash
|
||||
ssh banner@stapp03
|
||||
|
||||
# add prerequisites + Docker's official repo
|
||||
sudo yum install -y yum-utils
|
||||
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||
|
||||
# install docker-ce, CLI, containerd, and compose
|
||||
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
|
||||
# start + enable the service
|
||||
sudo systemctl enable --now docker
|
||||
```
|
||||
|
||||
## Task 36
|
||||
|
||||
The Nautilus DevOps team is conducting application deployment tests on selected application servers. They require a nginx container deployment on Application Server 1. Complete the task with the following instructions:
|
||||
|
||||
On Application Server 1 create a container named nginx_1 using the nginx image with the alpine tag. Ensure container is in a running state.
|
||||
|
||||
```bash
|
||||
ssh tony@stapp01
|
||||
|
||||
sudo docker run -d --name nginx_1 nginx:alpine
|
||||
|
||||
# verify
|
||||
sudo docker ps
|
||||
|
||||
## Task 37
|
||||
|
||||
The Nautilus DevOps team possesses confidential data on App Server 1 in the Stratos Datacenter. A container named ubuntu_latest is running on the same server.
|
||||
|
||||
Copy an encrypted file /tmp/nautilus.txt.gpg from the docker host to the ubuntu_latest container located at /opt/. Ensure the file is not modified during this operation.
|
||||
|
||||
```bash
|
||||
ssh tony@stapp01
|
||||
|
||||
docker cp /tmp/nautilus.txt.gpg ubuntu_latest:/opt/
|
||||
|
||||
# confirm it's in the container
|
||||
sudo docker exec ubuntu_latest ls -l /opt/nautilus.txt.gpg
|
||||
|
||||
# integrity check — compare checksums host vs container
|
||||
md5sum /tmp/nautilus.txt.gpg
|
||||
sudo docker exec ubuntu_latest md5sum /opt/nautilus.txt.gpg
|
||||
```
|
||||
|
||||
## Task 38
|
||||
|
||||
Nautilus project developers are planning to start testing on a new project. As per their meeting with the DevOps team, they want to test containerized environment application features. As per details shared with DevOps team, we need to accomplish the following task:
|
||||
|
||||
a. Pull busybox:musl image on App Server 1 in Stratos DC and re-tag (create new tag) this image as busybox:blog.
|
||||
|
||||
```bash
|
||||
ssh tony@stapp01
|
||||
|
||||
# a. pull the source image
|
||||
docker pull busybox:musl
|
||||
|
||||
# re-tag it as busybox:blog
|
||||
docker tag busybox:musl busybox:blog
|
||||
```
|
||||
|
||||
|
||||
## Task 39
|
||||
|
||||
One of the Nautilus developer was working to test new changes on a container. He wants to keep a backup of his changes to the container. A new request has been raised for the DevOps team to create a new image from this container. Below are more details about it:
|
||||
|
||||
a. Create an image news:xfusion on Application Server 1 from a container ubuntu_latest that is running on same server.
|
||||
|
||||
```bash
|
||||
ssh tony@stapp01
|
||||
|
||||
sudo docker commit ubuntu_latest news:xfusion
|
||||
sudo docker images news # news:xfusion present
|
||||
```
|
||||
|
||||
## Task 40
|
||||
|
||||
One of the Nautilus DevOps team members was working to configure services on a kkloud container that is running on App Server 3 in Stratos Datacenter. Due to some personal work he is on PTO for the rest of the week, but we need to finish his pending work ASAP. Please complete the remaining work as per details given below:
|
||||
|
||||
a. Install apache2 in kkloud container using apt that is running on App Server 3 in Stratos Datacenter.
|
||||
b. Configure Apache to listen on port 6300 instead of default http port. Do not bind it to listen on specific IP or hostname only, i.e it should listen on localhost, 127.0.0.1, container ip, etc.
|
||||
c. Make sure Apache service is up and running inside the container. Keep the container in running state at the end.
|
||||
|
||||
```bash
|
||||
ssh banner@stapp03
|
||||
# BigGr33n
|
||||
|
||||
docker exec -it kkloud bash
|
||||
|
||||
# in running container
|
||||
apt update
|
||||
apt install -y apache2
|
||||
|
||||
sed -i 's/^Listen 80$/Listen 6300/' /etc/apache2/ports.conf
|
||||
sed -i 's/<VirtualHost \*:80>/<VirtualHost *:6300>/' /etc/apache2/sites-enabled/000-default.conf
|
||||
|
||||
service apache2 start
|
||||
|
||||
# verify inside container
|
||||
# is it listening on 6300 on all interfaces?
|
||||
apt install -y net-tools 2>/dev/null
|
||||
netstat -tlnp | grep 6300
|
||||
# or
|
||||
ss -tlnp | grep 6300
|
||||
|
||||
# functional check
|
||||
curl http://localhost:6300
|
||||
367
100 - days of devops/devops-41-50.md
Normal file
367
100 - days of devops/devops-41-50.md
Normal file
@@ -0,0 +1,367 @@
|
||||
## Task 41
|
||||
|
||||
As per recent requirements shared by the Nautilus application development team, they need custom images created for one of their projects. Several of the initial testing requirements are already been shared with DevOps team. Therefore, create a docker file /opt/docker/Dockerfile (please keep D capital of Dockerfile) on App server 1 in Stratos DC and configure to build an image with the following requirements:
|
||||
|
||||
a. Use ubuntu:24.04 as the base image.
|
||||
|
||||
b. Install apache2 and configure it to work on 3000 port. (do not update any other Apache configuration settings like document root etc).
|
||||
|
||||
```bash
|
||||
ssh tony@stapp01
|
||||
|
||||
sudo mkdir -p /opt/docker
|
||||
|
||||
sudo tee /opt/docker/Dockerfile > /dev/null <<'EOF'
|
||||
FROM ubuntu:24.04
|
||||
|
||||
# install apache2 non-interactively
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y apache2 && \
|
||||
apt-get clean
|
||||
|
||||
# configure apache to listen on port 3000
|
||||
RUN sed -i 's/^Listen 80$/Listen 3000/' /etc/apache2/ports.conf && \
|
||||
sed -i 's/<VirtualHost \*:80>/<VirtualHost *:3000>/' /etc/apache2/sites-enabled/000-default.conf
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["apache2ctl", "-D", "FOREGROUND"]
|
||||
EOF
|
||||
```
|
||||
|
||||
|
||||
## Task 42
|
||||
|
||||
The Nautilus DevOps team needs to set up several docker environments for different applications. One of the team members has been assigned a ticket where he has been asked to create some docker networks to be used later. Complete the task based on the following ticket description:
|
||||
|
||||
a. Create a docker network named as news on App Server 3 in Stratos DC.
|
||||
b. Configure it to use macvlan drivers.
|
||||
c. Set it to use subnet 192.168.30.0/24 and iprange 192.168.30.0/24.
|
||||
|
||||
```
|
||||
```bash
|
||||
ssh banner@stapp03
|
||||
# BigGr33n
|
||||
|
||||
docker network create -d macvlan \
|
||||
--subnet 192.168.30.0/24 \
|
||||
--ip-range 192.168.30.0/24 \
|
||||
news
|
||||
|
||||
docker network inspect news
|
||||
```
|
||||
|
||||
## Task 43
|
||||
|
||||
The Nautilus DevOps team is planning to host an application on a nginx-based container. There are number of tickets already been created for similar tasks. One of the tickets has been assigned to set up a nginx container on Application Server 3 in Stratos Datacenter. Please perform the task as per details mentioned below:
|
||||
|
||||
a. Pull nginx:alpine docker image on Application Server 3.
|
||||
b. Create a container named demo using the image you pulled.
|
||||
c. Map host port 3000 to container port 80. Please keep the container in running state.
|
||||
|
||||
```bash
|
||||
ssh banner@stapp03
|
||||
# BigGr33n
|
||||
|
||||
# a. pull the image
|
||||
docker pull nginx:alpine
|
||||
|
||||
# b + c. run named container, map host 3000 → container 80, detached
|
||||
docker run -d --name demo -p 3000:80 nginx:alpine
|
||||
```
|
||||
|
||||
## Task 44
|
||||
|
||||
The Nautilus application development team shared static website content that needs to be hosted on the httpd web server using a containerised platform. The team has shared details with the DevOps team, and we need to set up an environment according to those guidelines. Below are the details:
|
||||
|
||||
a. On App Server 3 in Stratos DC create a container named httpd using a docker compose file /opt/docker/docker-compose.yml (please use the exact name for file).
|
||||
b. Use httpd (preferably latest tag) image for container and make sure container is named as httpd; you can use any name for service.
|
||||
c. Map 80 number port of container with port 3003 of docker host.
|
||||
d. Map container's /usr/local/apache2/htdocs volume with /opt/itadmin volume of docker host which is already there. (please do not modify any data within these locations).
|
||||
|
||||
```bash
|
||||
ssh banner@stapp03
|
||||
# BigGr33n
|
||||
|
||||
sudo mkdir -p /opt/docker
|
||||
|
||||
sudo tee /opt/docker/docker-compose.yml > /dev/null <<'EOF'
|
||||
version: "3"
|
||||
services:
|
||||
webserver:
|
||||
image: httpd:latest
|
||||
container_name: httpd
|
||||
ports:
|
||||
- "3003:80"
|
||||
volumes:
|
||||
- /opt/itadmin:/usr/local/apache2/htdocs
|
||||
EOF
|
||||
|
||||
cd /opt/docker
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
## Task 45
|
||||
|
||||
The Nautilus DevOps team is working to create new images per requirements shared by the development team. One of the team members is working to create a Dockerfile on App Server 2 in Stratos DC. While working on it she ran into issues in which the docker build is failing and displaying errors. Look into the issue and fix it to build an image as per details mentioned below:
|
||||
|
||||
a. The Dockerfile is placed on App Server 2 under /opt/docker directory.
|
||||
b. Fix the issues with this file and make sure it is able to build the image.
|
||||
c. Do not change base image, any other valid configuration within Dockerfile, or any of the data been used — for example, index.html.
|
||||
|
||||
Note: Please note that once you click on FINISH button all the existing containers will be destroyed and new image will be built from your Dockerfile.
|
||||
|
||||
```bash
|
||||
ssh steve@stapp02
|
||||
# Am3ric@
|
||||
|
||||
sudo tee /opt/docker/Dockerfile > /dev/null <<'EOF'
|
||||
FROM httpd:2.4.43
|
||||
WORKDIR /usr/local/apache2
|
||||
RUN sed -i "s/Listen 80/Listen 8080/g" /usr/local/apache2/conf/httpd.conf
|
||||
RUN sed -i '/LoadModule\ ssl_module modules\/mod_ssl.so/s/^#//g' conf/httpd.conf
|
||||
RUN sed -i '/LoadModule\ socache_shmcb_module modules\/mod_socache_shmcb.so/s/^#//g' conf/httpd.conf
|
||||
RUN sed -i '/Include\ conf\/extra\/httpd-ssl.conf/s/^#//g' conf/httpd.conf
|
||||
COPY certs/server.crt /usr/local/apache2/conf/server.crt
|
||||
COPY certs/server.key /usr/local/apache2/conf/server.key
|
||||
COPY html/index.html /usr/local/apache2/htdocs/
|
||||
EOF
|
||||
```
|
||||
|
||||
## Task 46
|
||||
|
||||
The Nautilus Application development team recently finished development of one of the apps that they want to deploy on a containerized platform. The Nautilus Application development and DevOps teams met to discuss some of the basic pre-requisites and requirements to complete the deployment. The team wants to test the deployment on one of the app servers before going live and set up a complete containerized stack using a docker compose fie. Below are the details of the task:
|
||||
|
||||
On App Server 1 in Stratos Datacenter create a docker compose file /opt/finance/docker-compose.yml (should be named exactly).
|
||||
|
||||
The compose should deploy two services (web and DB), and each service should deploy a container as per details below:
|
||||
|
||||
For web service:
|
||||
a. Container name must be php_blog.
|
||||
b. Use image php with any apache tag. Check here for more details.
|
||||
c. Map php_blog container's port 80 with host port 8089
|
||||
d. Map php_blog container's /var/www/html volume with host volume /var/www/html.
|
||||
|
||||
For DB service:
|
||||
a. Container name must be mysql_blog.
|
||||
b. Use image mariadb with any tag (preferably latest). Check here for more details.
|
||||
c. Map mysql_blog container's port 3306 with host port 3306
|
||||
d. Map mysql_blog container's /var/lib/mysql volume with host volume /var/lib/mysql.
|
||||
e. Set MYSQL_DATABASE=database_blog and use any custom user ( except root ) with some complex password for DB connections.
|
||||
|
||||
After running docker-compose up you can access the app with curl command curl <server-ip or hostname>:8089/
|
||||
For more details check here.
|
||||
|
||||
Note: Once you click on FINISH button, all currently running/stopped containers will be destroyed and stack will be deployed again using your compose file.
|
||||
|
||||
```bash
|
||||
ssh tony@stapp01
|
||||
# Ir0nM@n
|
||||
|
||||
sudo su -
|
||||
|
||||
sudo mkdir -p /opt/finance
|
||||
sudo tee /opt/finance/docker-compose.yml > /dev/null <<'EOF'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: php:apache
|
||||
container_name: php_blog
|
||||
ports:
|
||||
- "8089:80"
|
||||
volumes:
|
||||
- /var/www/html:/var/www/html
|
||||
db:
|
||||
image: mariadb:latest
|
||||
container_name: mysql_blog
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- /var/lib/mysql:/var/lib/mysql
|
||||
environment:
|
||||
MYSQL_DATABASE: database_blog
|
||||
MYSQL_USER: blog_user
|
||||
MYSQL_PASSWORD: Bl0g_P@ss2024
|
||||
MYSQL_ROOT_PASSWORD: R00t_P@ss2024
|
||||
EOF
|
||||
|
||||
cd /opt/finance
|
||||
|
||||
sudo docker compose up -d
|
||||
sudo docker ps # php_blog + mysql_blog, both Up
|
||||
curl http://localhost:8089/
|
||||
```
|
||||
|
||||
## Task 47
|
||||
|
||||
A python app needed to be Dockerized, and then it needs to be deployed on App Server 3. We have already copied a requirements.txt file (having the app dependencies) under /python_app/src/ directory on App Server 3. Further complete this task as per details mentioned below:
|
||||
|
||||
Create a Dockerfile under /python_app directory:
|
||||
|
||||
Use any python image as the base image.
|
||||
Install the dependencies using requirements.txt file.
|
||||
Expose the port 3004.
|
||||
Run the server.py script using CMD.
|
||||
|
||||
Build an image named nautilus/python-app using this Dockerfile.
|
||||
|
||||
Once image is built, create a container named pythonapp_nautilus:
|
||||
Map port 3004 of the container to the host port 8096.
|
||||
Once deployed, you can test the app using curl command on App Server 3.
|
||||
|
||||
|
||||
```bash
|
||||
ssh banner@stapp03
|
||||
# BigGr33n
|
||||
|
||||
sudo su -
|
||||
|
||||
ls -la /python_app/src/ # confirm server.py + requirements.txt here
|
||||
tee /python_app/Dockerfile > /dev/null <<'EOF'
|
||||
FROM python:3.12
|
||||
WORKDIR /python_app/src
|
||||
COPY src/requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY src/ .
|
||||
EXPOSE 3004
|
||||
CMD ["python", "server.py"]
|
||||
EOF
|
||||
cd /python_app
|
||||
|
||||
docker build -t nautilus/python-app .
|
||||
docker run -d --name pythonapp_nautilus -p 8096:3004 nautilus/python-app
|
||||
docker ps
|
||||
|
||||
curl http://localhost:8096/
|
||||
```
|
||||
|
||||
|
||||
### Task 48
|
||||
|
||||
The Nautilus DevOps team is diving into Kubernetes for application management. One team member has a task to create a pod according to the details below:
|
||||
|
||||
Create a pod named pod-httpd using the httpd image with the latest tag. Ensure to specify the tag as httpd:latest.
|
||||
Set the app label to httpd_app, and name the container as httpd-container.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Create pod-httpd
|
||||
|
||||
## Apply
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-httpd
|
||||
labels:
|
||||
app: httpd_app
|
||||
spec:
|
||||
containers:
|
||||
- name: httpd-container
|
||||
image: httpd:latest
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get pods
|
||||
kubectl get pod pod-httpd --show-labels
|
||||
kubectl describe pod pod-httpd
|
||||
```
|
||||
|
||||
Want: `pod-httpd` in `Running` state, label `app=httpd_app`, container named `httpd-container` running `httpd:latest`.
|
||||
|
||||
## Task 49
|
||||
|
||||
The Nautilus DevOps team is delving into Kubernetes for app management. One team member needs to create a deployment following these details:
|
||||
|
||||
Create a deployment named nginx to deploy the application nginx using the image nginx:latest (ensure to specify the tag)
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Create nginx deployment via heredoc
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get deployments
|
||||
kubectl get pods
|
||||
kubectl describe deployment nginx | grep -i image
|
||||
```
|
||||
|
||||
## Task 50
|
||||
|
||||
The Nautilus DevOps team has noticed performance issues in some Kubernetes-hosted applications due to resource constraints. To address this, they plan to set limits on resource utilization. Here are the details:
|
||||
|
||||
Create a pod named httpd-pod with a container named httpd-container. Use the httpd image with the latest tag (specify as httpd:latest). Configure the following container-level resource requests and limits for the container:
|
||||
|
||||
Requests: Memory: 15Mi, CPU: 100m
|
||||
Limits: Memory: 20Mi, CPU: 100m
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Create httpd-pod with resource limits
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: httpd-pod
|
||||
labels:
|
||||
app: httpd_app
|
||||
spec:
|
||||
containers:
|
||||
- name: httpd-container
|
||||
image: httpd:latest
|
||||
resources:
|
||||
requests:
|
||||
memory: "15Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "20Mi"
|
||||
cpu: "100m"
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get pod httpd-pod
|
||||
kubectl describe pod httpd-pod | grep -A6 -i limits
|
||||
```
|
||||
|
||||
Want: `httpd-pod` Running, and the describe output showing requests (15Mi/100m) + limits (20Mi/100m).
|
||||
575
100 - days of devops/devops-51-60.md
Normal file
575
100 - days of devops/devops-51-60.md
Normal file
@@ -0,0 +1,575 @@
|
||||
## Task 51
|
||||
|
||||
An application currently running on the Kubernetes cluster employs the nginx web server. The Nautilus application development team has introduced some recent changes that need deployment. They've crafted an image nginx:1.18 with the latest updates.
|
||||
|
||||
Execute a rolling update for this application, integrating the nginx:1.18 image. The deployment is named nginx-deployment.
|
||||
|
||||
Ensure all pods are operational post-update.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Rolling update nginx-deployment to nginx:1.18
|
||||
|
||||
## Check current state first
|
||||
|
||||
```bash
|
||||
kubectl get deployment nginx-deployment
|
||||
kubectl describe deployment nginx-deployment | grep -i image
|
||||
```
|
||||
|
||||
Note the container name from the describe output (needed for the set image command).
|
||||
|
||||
## Perform the rolling update
|
||||
|
||||
```bash
|
||||
kubectl set image deployment/nginx-deployment nginx-container=nginx:1.18
|
||||
```
|
||||
|
||||
Replace `nginx` (left of `=`) with the actual container name if it differs — check the describe output above.
|
||||
|
||||
## Watch the rollout
|
||||
|
||||
```bash
|
||||
kubectl rollout status deployment/nginx-deployment
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl rollout status deployment/nginx-deployment
|
||||
kubectl get pods
|
||||
kubectl describe deployment nginx-deployment | grep -i image
|
||||
```
|
||||
|
||||
Want: rollout `successfully rolled out`, all pods `Running`, image now `nginx:1.18`.
|
||||
|
||||
## Task 52
|
||||
|
||||
Earlier today, the Nautilus DevOps team deployed a new release for an application. However, a customer has reported a bug related to this recent release. Consequently, the team aims to revert to the previous version.
|
||||
|
||||
There exists a deployment named nginx-deployment; initiate a rollback to the previous revision.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Rollback nginx-deployment to previous revision
|
||||
|
||||
## Check history first (optional but good)
|
||||
|
||||
```bash
|
||||
kubectl rollout history deployment/nginx-deployment
|
||||
```
|
||||
|
||||
Shows the revisions — confirms there's a previous one to roll back to.
|
||||
|
||||
## Perform the rollback
|
||||
|
||||
```bash
|
||||
kubectl rollout undo deployment/nginx-deployment
|
||||
```
|
||||
|
||||
## Watch + verify
|
||||
|
||||
```bash
|
||||
kubectl rollout status deployment/nginx-deployment
|
||||
kubectl get pods
|
||||
kubectl describe deployment nginx-deployment | grep -i image
|
||||
```
|
||||
|
||||
Want: rollout `successfully rolled out`, all pods `Running`, image reverted to the previous version.
|
||||
|
||||
## Task 53
|
||||
|
||||
We encountered an issue with our Nginx and PHP-FPM setup on the Kubernetes cluster this morning, which halted its functionality. Investigate and rectify the issue:
|
||||
|
||||
The pod name is nginx-phpfpm and configmap name is nginx-config. Identify and fix the problem.
|
||||
|
||||
Once resolved, copy /home/thor/index.php file from the jump host to the nginx-container within the nginx document root. After this, you should be able to access the website using Website button on the top bar.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Fix nginx-phpfpm pod
|
||||
|
||||
## Step 1 — recon: inspect the pod and configmap
|
||||
|
||||
```bash
|
||||
# pod state + how it's configured
|
||||
kubectl describe pod nginx-phpfpm
|
||||
kubectl get pod nginx-phpfpm -o yaml
|
||||
|
||||
# the nginx config
|
||||
kubectl describe configmap nginx-config
|
||||
kubectl get configmap nginx-config -o yaml
|
||||
```
|
||||
|
||||
## Step 2 — identify the mismatch
|
||||
|
||||
Compare two things:
|
||||
- The **document root** in the nginx config (from the configmap) — look for `root /some/path;`
|
||||
- The **shared volume mountPath** in the pod spec — where the shared `emptyDir` volume is mounted in *both* the nginx and php-fpm containers
|
||||
|
||||
The bug: these two paths **don't match**. nginx serves from one path, but the shared volume (where files land) is mounted at a different path → nginx can't find the files.
|
||||
|
||||
## Step 3 — fix the mismatch
|
||||
|
||||
The fix is to make them consistent. Usually the **configmap's `root` directive** is edited to match the volume mountPath (or vice versa). Edit the configmap:
|
||||
|
||||
```bash
|
||||
kubectl edit configmap nginx-config
|
||||
```
|
||||
|
||||
Change the `root` line so it matches the shared volume's mountPath in the pod spec. Common correct value: `/var/www/html`.
|
||||
|
||||
## Step 4 — recreate the pod (configmap changes need a pod restart)
|
||||
|
||||
```bash
|
||||
kubectl get pod nginx-phpfpm -o yaml > /tmp/nginx-phpfpm.yaml
|
||||
kubectl delete pod nginx-phpfpm
|
||||
kubectl apply -f /tmp/nginx-phpfpm.yaml
|
||||
```
|
||||
|
||||
## Step 5 — copy index.php into the nginx container's docroot
|
||||
|
||||
```bash
|
||||
kubectl cp /home/thor/index.php nginx-phpfpm:/var/www/html/index.php -c nginx-container
|
||||
```
|
||||
|
||||
(Use the actual docroot path confirmed in step 2/3, and `-c nginx-container` to target the right container.)
|
||||
|
||||
## Step 6 — verify
|
||||
|
||||
```bash
|
||||
kubectl get pod nginx-phpfpm
|
||||
kubectl exec nginx-phpfpm -c nginx-container -- ls -l /var/www/html/
|
||||
```
|
||||
|
||||
Then hit the **Website** button.
|
||||
|
||||
|
||||
## Task 54
|
||||
|
||||
We are working on an application that will be deployed on multiple containers within a pod on Kubernetes cluster. There is a requirement to share a volume among the containers to save some temporary data. The Nautilus DevOps team is developing a similar template to replicate the scenario. Below you can find more details about it.
|
||||
|
||||
Create a pod named volume-share-nautilus.
|
||||
|
||||
For the first container, use image debian with latest tag only and remember to mention the tag i.e debian:latest, container should be named as volume-container-nautilus-1, and run a sleep command for it so that it remains in running state. Volume volume-share should be mounted at path /tmp/blog.
|
||||
|
||||
For the second container, use image debian with the latest tag only and remember to mention the tag i.e debian:latest, container should be named as volume-container-nautilus-2, and again run a sleep command for it so that it remains in running state. Volume volume-share should be mounted at path /tmp/games.
|
||||
|
||||
Volume name should be volume-share of type emptyDir.
|
||||
|
||||
After creating the pod, exec into the first container i.e volume-container-nautilus-1, and just for testing create a file blog.txt with the content Welcome to xFusionCorp Industries under the mounted path of first container i.e /tmp/blog.
|
||||
|
||||
The file blog.txt should be present under the mounted path /tmp/games on the second container volume-container-nautilus-2 as well, since they are using a shared volume.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Create volume-share-nautilus pod
|
||||
|
||||
## Create the pod
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: volume-share-nautilus
|
||||
spec:
|
||||
containers:
|
||||
- name: volume-container-nautilus-1
|
||||
image: debian:latest
|
||||
command: ["sleep", "infinity"]
|
||||
volumeMounts:
|
||||
- name: volume-share
|
||||
mountPath: /tmp/blog
|
||||
- name: volume-container-nautilus-2
|
||||
image: debian:latest
|
||||
command: ["sleep", "infinity"]
|
||||
volumeMounts:
|
||||
- name: volume-share
|
||||
mountPath: /tmp/games
|
||||
volumes:
|
||||
- name: volume-share
|
||||
emptyDir: {}
|
||||
EOF
|
||||
```
|
||||
|
||||
## Wait for it to be Running
|
||||
|
||||
```bash
|
||||
kubectl get pod volume-share-nautilus -w
|
||||
```
|
||||
|
||||
## Write the file in container 1
|
||||
|
||||
```bash
|
||||
kubectl exec volume-share-nautilus -c volume-container-nautilus-1 -- \
|
||||
bash -c 'echo "Welcome to xFusionCorp Industries" > /tmp/blog/blog.txt'
|
||||
```
|
||||
|
||||
## Verify it appears in container 2 (shared volume)
|
||||
|
||||
```bash
|
||||
kubectl exec volume-share-nautilus -c volume-container-nautilus-2 -- \
|
||||
cat /tmp/games/blog.txt
|
||||
```
|
||||
|
||||
Want: `Welcome to xFusionCorp Industries` — proving the emptyDir is shared across both containers at their respective mount paths.
|
||||
|
||||
|
||||
## Task 55
|
||||
|
||||
We have a web server container running the nginx image. The access and error logs generated by the web server are not critical enough to be placed on a persistent volume. However, Nautilus developers need access to the last 24 hours of logs so that they can trace issues and bugs. Therefore, we need to ship the access and error logs for the web server to a log-aggregation service. Following the separation of concerns principle, we implement the Sidecar pattern by deploying a second container that ships the error and access logs from nginx. Nginx does one thing, and it does it well - serving web pages. The second container also specializes in its task - shipping logs. Since containers are running on the same Pod, we can use a shared emptyDir volume to read and write logs.
|
||||
|
||||
Create a pod named webserver.
|
||||
|
||||
Create an emptyDir volume named shared-logs.
|
||||
|
||||
Create a regular container in the webserver pod from the nginx:latest image named nginx-container, and an init container from the ubuntu:latest image named sidecar-container.
|
||||
|
||||
Add the following command to the sidecar-container "sh","-c","while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"
|
||||
|
||||
Mount the shared-logs volume in both containers at /var/log/nginx. Ensure all containers are in a running state.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Create webserver sidecar pod
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: webserver
|
||||
spec:
|
||||
volumes:
|
||||
- name: shared-logs
|
||||
emptyDir: {}
|
||||
initContainers:
|
||||
- name: sidecar-container
|
||||
image: ubuntu:latest
|
||||
restartPolicy: Always
|
||||
command: ["sh", "-c", "while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"]
|
||||
volumeMounts:
|
||||
- name: shared-logs
|
||||
mountPath: /var/log/nginx
|
||||
containers:
|
||||
- name: nginx-container
|
||||
image: nginx:latest
|
||||
volumeMounts:
|
||||
- name: shared-logs
|
||||
mountPath: /var/log/nginx
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get pod webserver
|
||||
kubectl get pod webserver -o jsonpath='{.status.phase}{"\n"}'
|
||||
kubectl describe pod webserver | grep -A2 -i "state"
|
||||
```
|
||||
|
||||
Want: pod `Running`, both `nginx-container` and `sidecar-container` up (Ready/Running).
|
||||
|
||||
## Task 56
|
||||
|
||||
Some of the Nautilus team developers are developing a static website and they want to deploy it on Kubernetes cluster. They want it to be highly available and scalable. Therefore, based on the requirements, the DevOps team has decided to create a deployment for it with multiple replicas. Below you can find more details about it:
|
||||
|
||||
Create a deployment using nginx image with latest tag only and remember to mention the tag i.e nginx:latest. Name it as nginx-deployment. The container should be named as nginx-container, also make sure replica counts are 3.
|
||||
|
||||
Create a NodePort type service named nginx-service. The nodePort should be 30011.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# nginx deployment + NodePort service
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx-container
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
EOF
|
||||
```
|
||||
|
||||
## NodePort Service
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx-service
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: nginx
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
nodePort: 30011
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get deployment nginx-deployment
|
||||
kubectl get pods -l app=nginx
|
||||
kubectl get service nginx-service
|
||||
```
|
||||
|
||||
Want: deployment `3/3` ready, three pods Running, service `nginx-service` type NodePort exposing `30011`.
|
||||
|
||||
## Task 57
|
||||
|
||||
The Nautilus DevOps team is working on to setup some pre-requisites for an application that will send the greetings to different users. There is a sample deployment, that needs to be tested. Below is a scenario which needs to be configured on Kubernetes cluster. Please find below more details about it.
|
||||
|
||||
Create a pod named print-envars-greeting.
|
||||
|
||||
Configure spec as, the container name should be print-env-container and use bash image.
|
||||
|
||||
Create three environment variables:
|
||||
a. GREETING and its value should be Welcome to
|
||||
b. COMPANY and its value should be Stratos
|
||||
c. GROUP and its value should be Industries
|
||||
|
||||
Use command ["/bin/sh", "-c", 'echo "$(GREETING) $(COMPANY) $(GROUP)"'] (please use this exact command), also set its restartPolicy policy to Never to avoid crash loop back.
|
||||
|
||||
You can check the output using kubectl logs -f print-envars-greeting command.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Create print-envars-greeting pod
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: print-envars-greeting
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: print-env-container
|
||||
image: bash
|
||||
command: ["/bin/sh", "-c", 'echo "$(GREETING) $(COMPANY) $(GROUP)"']
|
||||
env:
|
||||
- name: GREETING
|
||||
value: "Welcome to"
|
||||
- name: COMPANY
|
||||
value: "Stratos"
|
||||
- name: GROUP
|
||||
value: "Industries"
|
||||
EOF
|
||||
```
|
||||
|
||||
## Check the output
|
||||
|
||||
```bash
|
||||
kubectl logs -f print-envars-greeting
|
||||
```
|
||||
|
||||
Want: `Welcome to Stratos Industries` in the logs, pod in `Completed` state.
|
||||
|
||||
## Task 58
|
||||
|
||||
The Nautilus DevOps teams is planning to set up a Grafana tool to collect and analyze analytics from some applications. They are planning to deploy it on Kubernetes cluster. Below you can find more details.
|
||||
|
||||
1.) Create a deployment named grafana-deployment-xfusion using any grafana image for Grafana app. Set other parameters as per your choice.
|
||||
2.) Create NodePort type service with nodePort 32000 to expose the app.
|
||||
|
||||
You do not need to make any configuration changes inside the Grafana app once deployed; just make sure you can access the Grafana login page.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Grafana deployment + NodePort service
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: grafana-deployment-xfusion
|
||||
labels:
|
||||
app: grafana-deployment-xfusion
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: grafana-deployment-xfusion
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: grafana-deployment-xfusion
|
||||
spec:
|
||||
containers:
|
||||
- name: grafana
|
||||
image: grafana/grafana:latest
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: grafana-service
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: grafana-deployment-xfusion
|
||||
ports:
|
||||
- port: 32000
|
||||
targetPort: 3000
|
||||
nodePort: 32000
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get deployment grafana-deployment-xfusion
|
||||
kubectl get pods -l app=grafana
|
||||
kubectl get service grafana-service
|
||||
kubectl rollout status deployment/grafana-deployment-xfusion
|
||||
```
|
||||
|
||||
Want: deployment `1/1` ready, pod Running, service exposing `32000`. Then hit the app — Grafana login page should load.
|
||||
|
||||
## Task 59
|
||||
|
||||
Last week, the Nautilus DevOps team deployed a redis app on Kubernetes cluster, which was working fine so far. This morning one of the team members was making some changes in this existing setup, but he made some mistakes and the app went down. We need to fix this as soon as possible. Please take a look.
|
||||
|
||||
The deployment name is redis-deployment. The pods are not in running state right now, so please look into the issue and fix the same.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
- incorrect config map name
|
||||
- incorrect image on redis
|
||||
|
||||
## Task 60
|
||||
|
||||
The Nautilus DevOps team is working on a Kubernetes template to deploy a web application on the cluster. There are some requirements to create/use persistent volumes to store the application code, and the template needs to be designed accordingly. Please find more details below:
|
||||
|
||||
Create a PersistentVolume named as pv-devops. Configure the spec as storage class should be manual, set capacity to 4Gi, set access mode to ReadWriteOnce, volume type should be hostPath and set path to /mnt/devops (this directory is already created, you might not be able to access it directly, so you need not to worry about it).
|
||||
|
||||
Create a PersistentVolumeClaim named as pvc-devops. Configure the spec as storage class should be manual, request 1Gi of the storage, set access mode to ReadWriteOnce.
|
||||
|
||||
Create a pod named as pod-devops, mount the persistent volume you created with claim name pvc-devops at document root of the web server, the container within the pod should be named as container-devops using image nginx with latest tag only (remember to mention the tag i.e nginx:latest).
|
||||
|
||||
Create a node port type service named web-devops using node port 30008 to expose the web server running within the pod.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# PV + PVC + Pod + Service (devops)
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: pv-devops
|
||||
spec:
|
||||
storageClassName: manual
|
||||
capacity:
|
||||
storage: 4Gi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
hostPath:
|
||||
path: /mnt/devops
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: pvc-devops
|
||||
spec:
|
||||
storageClassName: manual
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-devops
|
||||
labels:
|
||||
app: web-devops
|
||||
spec:
|
||||
containers:
|
||||
- name: container-devops
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumeMounts:
|
||||
- name: web-storage
|
||||
mountPath: /usr/share/nginx/html
|
||||
volumes:
|
||||
- name: web-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: pvc-devops
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: web-devops
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: web-devops
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
nodePort: 30008
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get pv pv-devops
|
||||
kubectl get pvc pvc-devops
|
||||
kubectl get pod pod-devops
|
||||
kubectl get svc web-devops
|
||||
```
|
||||
|
||||
Want: PV `Bound`, PVC `Bound`, pod `Running`, service exposing `30008`.
|
||||
994
100 - days of devops/devops-61-70.md
Normal file
994
100 - days of devops/devops-61-70.md
Normal file
@@ -0,0 +1,994 @@
|
||||
## Task 61
|
||||
|
||||
There are some applications that need to be deployed on Kubernetes cluster and these apps have some pre-requisites where some configurations need to be changed before deploying the app container. Some of these changes cannot be made inside the images so the DevOps team has come up with a solution to use init containers to perform these tasks during deployment. Below is a sample scenario that the team is going to test first.
|
||||
|
||||
Create a Deployment named as ic-deploy-xfusion.
|
||||
|
||||
Configure spec as replicas should be 1, labels app should be ic-xfusion, template's metadata lables app should be the same ic-xfusion.
|
||||
|
||||
The initContainers should be named as ic-msg-xfusion, use image fedora with latest tag and use command '/bin/bash', '-c' and 'echo Init Done - Welcome to xFusionCorp Industries > /ic/blog'. The volume mount should be named as ic-volume-xfusion and mount path should be /ic.
|
||||
|
||||
Main container should be named as ic-main-xfusion, use image fedora with latest tag and use command '/bin/bash', '-c' and 'while true; do cat /ic/blog; sleep 5; done'. The volume mount should be named as ic-volume-xfusion and mount path should be /ic.
|
||||
|
||||
Volume to be named as ic-volume-xfusion and it should be an emptyDir type.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Create ic-deploy-xfusion deployment
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ic-deploy-xfusion
|
||||
labels:
|
||||
app: ic-xfusion
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ic-xfusion
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ic-xfusion
|
||||
spec:
|
||||
initContainers:
|
||||
- name: ic-msg-xfusion
|
||||
image: fedora:latest
|
||||
command: ["/bin/bash", "-c", "echo Init Done - Welcome to xFusionCorp Industries > /ic/blog"]
|
||||
volumeMounts:
|
||||
- name: ic-volume-xfusion
|
||||
mountPath: /ic
|
||||
containers:
|
||||
- name: ic-main-xfusion
|
||||
image: fedora:latest
|
||||
command: ["/bin/bash", "-c", "while true; do cat /ic/blog; sleep 5; done"]
|
||||
volumeMounts:
|
||||
- name: ic-volume-xfusion
|
||||
mountPath: /ic
|
||||
volumes:
|
||||
- name: ic-volume-xfusion
|
||||
emptyDir: {}
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get deployment ic-deploy-xfusion
|
||||
kubectl get pods -l app=ic-xfusion
|
||||
kubectl logs -l app=ic-xfusion -c ic-main-xfusion
|
||||
```
|
||||
|
||||
Want: deployment `1/1` ready, pod Running, and the main container's logs printing `Init Done - Welcome to xFusionCorp Industries` every 5s.
|
||||
|
||||
## Task 62
|
||||
|
||||
The Nautilus DevOps team is working to deploy some tools in Kubernetes cluster. Some of the tools are licence based so that licence information needs to be stored securely within Kubernetes cluster. Therefore, the team wants to utilize Kubernetes secrets to store those secrets. Below you can find more details about the requirements:
|
||||
|
||||
We already have a secret key file ecommerce.txt under the /opt/ directory. Create a generic secret named ecommerce, it should contain the password/license-number present in ecommerce.txt file.
|
||||
|
||||
Also create a pod named secret-nautilus.
|
||||
|
||||
Configure pod's spec as container name should be secret-container-nautilus, image should be debian with latest tag (remember to mention the tag with image). Use sleep command for container so that it remains in running state. Consume the created secret and mount it under /opt/games within the container.
|
||||
|
||||
To verify you can exec into the container secret-container-nautilus, to check the secret key under the mounted path /opt/games. Before hitting the Check button please make sure pod/pods are in running state, also validation can take some time to complete so keep patience.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Create secret + pod that mounts it
|
||||
|
||||
## Step 1 — create the generic secret from the file
|
||||
|
||||
```bash
|
||||
kubectl create secret generic ecommerce --from-file=/opt/ecommerce.txt
|
||||
```
|
||||
|
||||
## Step 2 — create the pod mounting the secret at /opt/games
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: secret-nautilus
|
||||
spec:
|
||||
containers:
|
||||
- name: secret-container-nautilus
|
||||
image: debian:latest
|
||||
command: ["sleep", "infinity"]
|
||||
volumeMounts:
|
||||
- name: secret-volume
|
||||
mountPath: /opt/games
|
||||
volumes:
|
||||
- name: secret-volume
|
||||
secret:
|
||||
secretName: ecommerce
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get secret ecommerce
|
||||
kubectl get pod secret-nautilus
|
||||
kubectl exec secret-nautilus -- ls -l /opt/games
|
||||
kubectl exec secret-nautilus -- cat /opt/games/ecommerce.txt
|
||||
```
|
||||
|
||||
Want: secret `ecommerce` exists, pod `Running`, and `/opt/games/ecommerce.txt` present with the license content inside the container.
|
||||
|
||||
## Task 63
|
||||
|
||||
There is an iron gallery app that the Nautilus DevOps team was developing. They have recently customized the app and are going to deploy the same on the Kubernetes cluster. Below you can find more details:
|
||||
|
||||
Create a namespace iron-namespace-nautilus
|
||||
|
||||
Create a deployment iron-gallery-deployment-nautilus for iron gallery under the same namespace you created.
|
||||
|
||||
:- Labels run should be iron-gallery.
|
||||
:- Replicas count should be 1.
|
||||
:- Selector's matchLabels run should be iron-gallery.
|
||||
:- Template labels run should be iron-gallery under metadata.
|
||||
:- The container should be named as iron-gallery-container-nautilus, use kodekloud/irongallery:2.0 image ( use exact image name / tag ).
|
||||
:- Resources limits for memory should be 100Mi and for CPU should be 50m.
|
||||
:- First volumeMount name should be config, its mountPath should be /usr/share/nginx/html/data.
|
||||
:- Second volumeMount name should be images, its mountPath should be /usr/share/nginx/html/uploads.
|
||||
:- First volume name should be config and give it emptyDir and second volume name should be images, also give it emptyDir.
|
||||
|
||||
Create a deployment iron-db-deployment-nautilus for iron db under the same namespace.
|
||||
:- Labels db should be mariadb.
|
||||
:- Replicas count should be 1.
|
||||
:- Selector's matchLabels db should be mariadb.
|
||||
:- Template labels db should be mariadb under metadata.
|
||||
:- The container name should be iron-db-container-nautilus, use kodekloud/irondb:2.0 image ( use exact image name / tag ).
|
||||
:- Define environment, set MYSQL_DATABASE its value should be database_apache, set MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD value should be with some complex passwords for DB connections, and MYSQL_USER value should be any custom user ( except root ).
|
||||
|
||||
:- Volume mount name should be db and its mountPath should be /var/lib/mysql. Volume name should be db and give it an emptyDir.
|
||||
|
||||
Create a service for iron db which should be named iron-db-service-nautilus under the same namespace. Configure spec as selector's db should be mariadb. Protocol should be TCP, port and targetPort should be 3306 and its type should be ClusterIP.
|
||||
|
||||
Create a service for iron gallery which should be named iron-gallery-service-nautilus under the same namespace. Configure spec as selector's run should be iron-gallery. Protocol should be TCP, port and targetPort should be 80, nodePort should be 32678 and its type should be NodePort.
|
||||
|
||||
Note:
|
||||
We don't need to make connection b/w database and front-end now, if the installation page is coming up it should be enough for now.
|
||||
|
||||
The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
# Iron Gallery app — namespace, deployments, services
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: iron-namespace-nautilus
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: iron-gallery-deployment-nautilus
|
||||
namespace: iron-namespace-nautilus
|
||||
labels:
|
||||
run: iron-gallery
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
run: iron-gallery
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
run: iron-gallery
|
||||
spec:
|
||||
containers:
|
||||
- name: iron-gallery-container-nautilus
|
||||
image: kodekloud/irongallery:2.0
|
||||
resources:
|
||||
limits:
|
||||
memory: "100Mi"
|
||||
cpu: "50m"
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /usr/share/nginx/html/data
|
||||
- name: images
|
||||
mountPath: /usr/share/nginx/html/uploads
|
||||
volumes:
|
||||
- name: config
|
||||
emptyDir: {}
|
||||
- name: images
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: iron-db-deployment-nautilus
|
||||
namespace: iron-namespace-nautilus
|
||||
labels:
|
||||
db: mariadb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
db: mariadb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
db: mariadb
|
||||
spec:
|
||||
containers:
|
||||
- name: iron-db-container-nautilus
|
||||
image: kodekloud/irondb:2.0
|
||||
env:
|
||||
- name: MYSQL_DATABASE
|
||||
value: "database_apache"
|
||||
- name: MYSQL_ROOT_PASSWORD
|
||||
value: "R00t_C0mpl3x_P@ss"
|
||||
- name: MYSQL_PASSWORD
|
||||
value: "Us3r_C0mpl3x_P@ss"
|
||||
- name: MYSQL_USER
|
||||
value: "iron_user"
|
||||
volumeMounts:
|
||||
- name: db
|
||||
mountPath: /var/lib/mysql
|
||||
volumes:
|
||||
- name: db
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: iron-db-service-nautilus
|
||||
namespace: iron-namespace-nautilus
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
db: mariadb
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 3306
|
||||
targetPort: 3306
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: iron-gallery-service-nautilus
|
||||
namespace: iron-namespace-nautilus
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
run: iron-gallery
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
nodePort: 32678
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get all -n iron-namespace-nautilus
|
||||
kubectl get pods -n iron-namespace-nautilus
|
||||
kubectl get svc -n iron-namespace-nautilus
|
||||
```
|
||||
|
||||
Want: both deployments `1/1`, both pods Running, db service ClusterIP on 3306, gallery service NodePort on 32678. Then the iron gallery install page should load via the nodePort.
|
||||
|
||||
## Task 64
|
||||
|
||||
One of the DevOps engineers was trying to deploy a python app on Kubernetes cluster. Unfortunately, due to some mis-configuration, the application is not coming up. Please take a look into it and fix the issues. Application should be accessible on the specified nodePort.
|
||||
|
||||
The deployment name is python-deployment-devops, its using poroko/flask-demo-app image. The deployment and service of this app is already deployed.
|
||||
|
||||
nodePort should be 32345 and targetPort should be python flask app's default port.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
### Solution
|
||||
|
||||
```bash
|
||||
kubectl set image deployment/python-deployment-devops python-container-devops=poroko/flask-demo-app
|
||||
|
||||
kubectl patch svc python-service-devops --type=json -p='[
|
||||
{"op":"replace","path":"/spec/ports/0/targetPort","value":5000},
|
||||
{"op":"replace","path":"/spec/ports/0/nodePort","value":32345}
|
||||
]'
|
||||
```
|
||||
|
||||
## Task 65
|
||||
|
||||
The Nautilus application development team observed some performance issues with one of the application that is deployed in Kubernetes cluster. After looking into number of factors, the team has suggested to use some in-memory caching utility for DB service. After number of discussions, they have decided to use Redis. Initially they would like to deploy Redis on kubernetes cluster for testing and later they will move it to production. Please find below more details about the task:
|
||||
|
||||
Create a redis deployment with following parameters:
|
||||
|
||||
Create a config map called my-redis-config having maxmemory 2mb in redis-config.
|
||||
|
||||
Name of the deployment should be redis-deployment, it should use
|
||||
redis:alpine image and container name should be redis-container. Also make sure it has only 1 replica.
|
||||
|
||||
The container should request for 1 CPU.
|
||||
|
||||
Mount 2 volumes:
|
||||
a. An Empty directory volume called data at path /redis-master-data.
|
||||
b. A configmap volume called redis-config at path /redis-master.
|
||||
c. The container should expose the port 6379.
|
||||
|
||||
Finally, redis-deployment should be up and running.
|
||||
|
||||
### Solution
|
||||
|
||||
# Redis deployment with configmap
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: my-redis-config
|
||||
data:
|
||||
redis-config: |
|
||||
maxmemory 2mb
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis-deployment
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
containers:
|
||||
- name: redis-container
|
||||
image: redis:alpine
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1"
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /redis-master-data
|
||||
- name: redis-config
|
||||
mountPath: /redis-master
|
||||
volumes:
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
- name: redis-config
|
||||
configMap:
|
||||
name: my-redis-config
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get configmap my-redis-config
|
||||
kubectl get deployment redis-deployment
|
||||
kubectl get pods -l app=redis
|
||||
kubectl describe pod -l app=redis | grep -A3 -i "requests\|mounts"
|
||||
```
|
||||
|
||||
Want: configmap exists, deployment `1/1` ready, pod Running, CPU request 1, both volumes mounted.
|
||||
|
||||
## Task 66
|
||||
|
||||
A new MySQL server needs to be deployed on Kubernetes cluster. The Nautilus DevOps team was working on to gather the requirements. Recently they were able to finalize the requirements and shared them with the team members to start working on it. Below you can find the details:
|
||||
|
||||
1.) Create a PersistentVolume mysql-pv, its capacity should be 250Mi, set other parameters as per your preference.
|
||||
|
||||
2.) Create a PersistentVolumeClaim to request this PersistentVolume storage. Name it as mysql-pv-claim and request a 250Mi of storage. Set other parameters as per your preference.
|
||||
|
||||
3.) Create a deployment named mysql-deployment, use any mysql image as per your preference. Mount the PersistentVolume at mount path /var/lib/mysql.
|
||||
|
||||
4.) Create a NodePort type service named mysql and set nodePort to 30007.
|
||||
|
||||
5.) Create a secret named mysql-root-pass having a key pair value, where key is password and its value is YUIidhb667, create another secret named mysql-user-pass having some key pair values, where first key is username and its value is kodekloud_pop, second key is password and value is BruCStnMT5, create one more secret named mysql-db-url, key name is database and value is kodekloud_db10
|
||||
|
||||
6.) Define some environment variables within the container:
|
||||
a.) name: MYSQL_ROOT_PASSWORD, should pick value from secretKeyRef name: mysql-root-pass and key: password
|
||||
b.) name: MYSQL_DATABASE, should pick value from secretKeyRef name: mysql-db-url and key: database
|
||||
c.) name: MYSQL_USER, should pick value from secretKeyRef name: mysql-user-pass key key: username
|
||||
d.) name: MYSQL_PASSWORD, should pick value from secretKeyRef name: mysql-user-pass and key: password
|
||||
|
||||
### Solution
|
||||
|
||||
# MySQL deployment with secrets, PV/PVC, service
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mysql-root-pass
|
||||
type: Opaque
|
||||
stringData:
|
||||
password: YUIidhb667
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mysql-user-pass
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: kodekloud_pop
|
||||
password: BruCStnMT5
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mysql-db-url
|
||||
type: Opaque
|
||||
stringData:
|
||||
database: kodekloud_db10
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: mysql-pv
|
||||
spec:
|
||||
storageClassName: manual
|
||||
capacity:
|
||||
storage: 250Mi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
hostPath:
|
||||
path: /mnt/mysql-data
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: mysql-pv-claim
|
||||
spec:
|
||||
storageClassName: manual
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 250Mi
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mysql-deployment
|
||||
labels:
|
||||
app: mysql
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mysql
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mysql
|
||||
spec:
|
||||
containers:
|
||||
- name: mysql
|
||||
image: mysql:8.0
|
||||
ports:
|
||||
- containerPort: 3306
|
||||
env:
|
||||
- name: MYSQL_ROOT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mysql-root-pass
|
||||
key: password
|
||||
- name: MYSQL_DATABASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mysql-db-url
|
||||
key: database
|
||||
- name: MYSQL_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mysql-user-pass
|
||||
key: username
|
||||
- name: MYSQL_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mysql-user-pass
|
||||
key: password
|
||||
volumeMounts:
|
||||
- name: mysql-storage
|
||||
mountPath: /var/lib/mysql
|
||||
volumes:
|
||||
- name: mysql-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: mysql-pv-claim
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mysql
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: mysql
|
||||
ports:
|
||||
- port: 3306
|
||||
targetPort: 3306
|
||||
nodePort: 30007
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get secrets
|
||||
kubectl get pv mysql-pv
|
||||
kubectl get pvc mysql-pv-claim
|
||||
kubectl get deployment mysql-deployment
|
||||
kubectl get pods -l app=mysql
|
||||
kubectl get svc mysql
|
||||
```
|
||||
|
||||
Want: 3 secrets, PV+PVC `Bound`, deployment `1/1`, pod Running, service NodePort 30007.
|
||||
|
||||
## Task 67
|
||||
|
||||
The Nautilus Application development team has finished development of one of the applications and it is ready for deployment. It is a guestbook application that will be used to manage entries for guests/visitors. As per discussion with the DevOps team, they have finalized the infrastructure that will be deployed on Kubernetes cluster. Below you can find more details about it.
|
||||
|
||||
BACK-END TIER
|
||||
|
||||
Create a deployment named redis-master for Redis master.
|
||||
|
||||
a.) Replicas count should be 1.
|
||||
b.) Container name should be master-redis-devops and it should use image redis.
|
||||
c.) Request resources as CPU should be 100m and Memory should be 100Mi.
|
||||
d.) Container port should be redis default port i.e 6379.
|
||||
|
||||
Create a service named redis-master for Redis master. Port and targetPort should be Redis default port i.e 6379.
|
||||
|
||||
Create another deployment named redis-slave for Redis slave.
|
||||
|
||||
a.) Replicas count should be 2.
|
||||
b.) Container name should be slave-redis-devops and it should use gcr.io/google_samples/gb-redisslave:v3 image.
|
||||
c.) Requests resources as CPU should be 100m and Memory should be 100Mi.
|
||||
d.) Define an environment variable named GET_HOSTS_FROM and its value should be dns.
|
||||
e.) Container port should be Redis default port i.e 6379.
|
||||
|
||||
Create another service named redis-slave. It should use Redis default port i.e 6379.
|
||||
|
||||
Create another service named redis-follower. Port and targetPort should be Redis default port i.e 6379. Its selector app should be redis-slave.
|
||||
|
||||
FRONT END TIER
|
||||
|
||||
Create a deployment named frontend.
|
||||
|
||||
a.) Replicas count should be 3.
|
||||
b.) Container name should be php-redis-devops and it should use gcr.io/google-samples/gb-frontend@sha256:a908df8486ff66f2c4daa0d3d8a2fa09846a1fc8efd65649c0109695c7c5cbff image.
|
||||
c.) Request resources as CPU should be 100m and Memory should be 100Mi.
|
||||
d.) Define an environment variable named as GET_HOSTS_FROM and its value should be dns.
|
||||
e.) Container port should be 80.
|
||||
|
||||
Create a service named frontend. Its type should be NodePort, port should be 80 and its nodePort should be 30009.
|
||||
|
||||
Finally, you can check the guestbook app by clicking on App button.
|
||||
|
||||
You can use any labels as per your choice.
|
||||
|
||||
### Solution
|
||||
|
||||
# Guestbook app — full stack
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
# ===== BACK-END: redis-master =====
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis-master
|
||||
labels:
|
||||
app: redis-master
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis-master
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis-master
|
||||
spec:
|
||||
containers:
|
||||
- name: master-redis-devops
|
||||
image: redis
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "100Mi"
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-master
|
||||
spec:
|
||||
selector:
|
||||
app: redis-master
|
||||
ports:
|
||||
- port: 6379
|
||||
targetPort: 6379
|
||||
---
|
||||
# ===== BACK-END: redis-slave =====
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis-slave
|
||||
labels:
|
||||
app: redis-slave
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis-slave
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis-slave
|
||||
spec:
|
||||
containers:
|
||||
- name: slave-redis-devops
|
||||
image: gcr.io/google_samples/gb-redisslave:v3
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "100Mi"
|
||||
env:
|
||||
- name: GET_HOSTS_FROM
|
||||
value: dns
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-slave
|
||||
spec:
|
||||
selector:
|
||||
app: redis-slave
|
||||
ports:
|
||||
- port: 6379
|
||||
targetPort: 6379
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-follower
|
||||
spec:
|
||||
selector:
|
||||
app: redis-slave
|
||||
ports:
|
||||
- port: 6379
|
||||
targetPort: 6379
|
||||
---
|
||||
# ===== FRONT-END =====
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: frontend
|
||||
labels:
|
||||
app: frontend
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: frontend
|
||||
spec:
|
||||
containers:
|
||||
- name: php-redis-devops
|
||||
image: gcr.io/google-samples/gb-frontend@sha256:a908df8486ff66f2c4daa0d3d8a2fa09846a1fc8efd65649c0109695c7c5cbff
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "100Mi"
|
||||
env:
|
||||
- name: GET_HOSTS_FROM
|
||||
value: dns
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: frontend
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: frontend
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
nodePort: 30009
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get deployments
|
||||
kubectl get pods
|
||||
kubectl get svc
|
||||
kubectl get pods -o wide | grep -E "redis|frontend"
|
||||
```
|
||||
|
||||
Want: redis-master `1/1`, redis-slave `2/2`, frontend `3/3`, all four services present, frontend NodePort 30009. Then the App button loads the guestbook.
|
||||
|
||||
## Task 68
|
||||
|
||||
The DevOps team at xFusionCorp Industries is initiating the setup of CI/CD pipelines and has decided to utilize Jenkins as their server. Execute the task according to the provided requirements:
|
||||
|
||||
1. Install Jenkins on the jenkins server using the apt utility only, and start it using the service command.
|
||||
|
||||
If you face a timeout issue while starting the Jenkins service, first check the service status with service jenkins status
|
||||
Then review the logs in /var/log/jenkins/jenkins.log to identify the cause.
|
||||
2. Jenkin's admin user name should be theadmin, password should be Adm!n321, full name should be Mariyam and email should be mariyam@jenkins.stratos.xfusioncorp.com.
|
||||
|
||||
|
||||
Note:
|
||||
1. To access the jenkins server, connect from the jump host using the root user with the password S3curePass.
|
||||
|
||||
2. After Jenkins server installation, click the Jenkins button on the top bar to access the Jenkins UI and follow on-screen instructions to create an admin user.
|
||||
|
||||
### Solution
|
||||
|
||||
# Install Jenkins on jenkins server + create admin user
|
||||
|
||||
## Access the server
|
||||
|
||||
From the jump host, SSH in as root:
|
||||
|
||||
```bash
|
||||
ssh root@jenkins
|
||||
# password: S3curePass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 1 — Install Jenkins (CLI)
|
||||
|
||||
### Step 1: Install Java (Jenkins prerequisite)
|
||||
|
||||
```bash
|
||||
apt update
|
||||
apt install -y fontconfig openjdk-17-jre
|
||||
java -version
|
||||
|
||||
apt update
|
||||
apt install -y openjdk-21-jre
|
||||
|
||||
# make Java 21 the default (in case 17 is still selected)
|
||||
update-alternatives --config java
|
||||
```
|
||||
|
||||
### Step 2: Add the Jenkins apt repository + signing key
|
||||
|
||||
```bash
|
||||
wget -O /usr/share/keyrings/jenkins-keyring.asc \
|
||||
https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
|
||||
|
||||
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc]" \
|
||||
"https://pkg.jenkins.io/debian-stable binary/" | \
|
||||
tee /etc/apt/sources.list.d/jenkins.list > /dev/null
|
||||
```
|
||||
|
||||
### Step 3: Install Jenkins
|
||||
|
||||
```bash
|
||||
apt update
|
||||
apt install -y jenkins
|
||||
```
|
||||
|
||||
### Step 4: Start Jenkins using the service command
|
||||
|
||||
```bash
|
||||
service jenkins start
|
||||
service jenkins status
|
||||
```
|
||||
|
||||
### If you hit a startup timeout
|
||||
|
||||
```bash
|
||||
service jenkins status
|
||||
cat /var/log/jenkins/jenkins.log
|
||||
```
|
||||
|
||||
The log identifies the cause — most commonly a missing/wrong Java version. Ensure `openjdk-17-jre` is installed and set as default (`update-alternatives --config java`), then restart:
|
||||
|
||||
```bash
|
||||
service jenkins restart
|
||||
```
|
||||
|
||||
Jenkins can take 60–90s on first start; a first-attempt timeout doesn't always mean failure — re-check status after waiting.
|
||||
|
||||
---
|
||||
|
||||
## Part 2 — Create the admin user (Web UI)
|
||||
|
||||
### Step 1: Get the initial unlock password
|
||||
|
||||
```bash
|
||||
cat /var/lib/jenkins/secrets/initialAdminPassword
|
||||
```
|
||||
|
||||
Copy the output.
|
||||
|
||||
### Step 2: Open the Jenkins UI
|
||||
|
||||
Click the **Jenkins** button on the top bar.
|
||||
|
||||
### Step 3: Unlock Jenkins
|
||||
|
||||
Paste the initial admin password from Step 1 → **Continue**.
|
||||
|
||||
### Step 4: Install plugins
|
||||
|
||||
Choose **"Install suggested plugins"** and wait for completion.
|
||||
|
||||
### Step 5: Create the First Admin User
|
||||
|
||||
Fill in exactly:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Username | `theadmin` |
|
||||
| Password | `Adm!n321` |
|
||||
| Full name | `Mariyam` |
|
||||
| Email | `mariyam@jenkins.stratos.xfusioncorp.com` |
|
||||
|
||||
Click **Save and Continue** → **Save and Finish** → **Start using Jenkins**.
|
||||
|
||||
---
|
||||
|
||||
## Verify
|
||||
|
||||
- `service jenkins status` shows Jenkins active/running.
|
||||
- You can log into the Jenkins UI as `theadmin` / `Adm!n321`.
|
||||
- The dashboard loads with `theadmin` as the logged-in user.
|
||||
|
||||
---
|
||||
|
||||
## Key points
|
||||
|
||||
- **Java first** — Jenkins is a Java app; without a JRE it won't start (the exact timeout the task warns about). Java 17 is the safe LTS-compatible choice.
|
||||
- **Repo + keyring required** — Jenkins isn't in Ubuntu base repos; add `pkg.jenkins.io` with its signing key before `apt install jenkins`.
|
||||
- **`debian-stable`** = Jenkins LTS line (more stable than weekly).
|
||||
- **`service jenkins start`** per the task (not `systemctl`).
|
||||
- **`initialAdminPassword`** unlocks the first UI screen before you can create `theadmin`.
|
||||
- **Admin user is created via the web wizard**, not CLI — the four field values must be exact for the grader.
|
||||
- Take a screenshot of the finished admin-user setup / dashboard for review.
|
||||
|
||||
## Task 69
|
||||
|
||||
The Nautilus DevOps team has recently setup a Jenkins server, which they want to use for some CI/CD jobs. Before that they want to install some plugins which will be used in most of the jobs. Please find below more details about the task
|
||||
|
||||
1. Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and password Adm!n321.
|
||||
2. Once logged in, install the Git and GitLab plugins. You may need to restart Jenkins to complete the plugin installation; if required, opt to Restart Jenkins when installation is complete and no jobs are running on the plugin installation/update page (Update Centre).
|
||||
|
||||
Note:
|
||||
|
||||
1. After restarting Jenkins, wait for the login page to reappear before proceeding.
|
||||
2. For tasks involving web UI changes, capture screenshots to share for review or consider using screen recording software like loom.com for documentation and sharing.
|
||||
|
||||
### Solution
|
||||
|
||||
# Install Git + GitLab plugins in Jenkins
|
||||
|
||||
## Step 1 — Log in
|
||||
|
||||
Click the **Jenkins** button on the top bar. Log in with:
|
||||
- Username: `admin`
|
||||
- Password: `Adm!n321`
|
||||
|
||||
## Step 2 — Navigate to Plugin Manager
|
||||
|
||||
**Manage Jenkins** → **Plugins** (under System Configuration; older versions: **Manage Plugins**).
|
||||
|
||||
## Step 3 — Find and select the plugins
|
||||
|
||||
Go to the **Available plugins** tab.
|
||||
|
||||
In the search box, search and check each:
|
||||
- Search `Git` → check the **Git** plugin
|
||||
- Search `GitLab` → check the **GitLab** plugin
|
||||
|
||||
## Step 4 — Install
|
||||
|
||||
Click **Install** (or **Download now and install after restart**).
|
||||
|
||||
## Step 5 — Restart Jenkins
|
||||
|
||||
On the installation progress page, check the box:
|
||||
**"Restart Jenkins when installation is complete and no jobs are running"**
|
||||
|
||||
Jenkins restarts. **Wait for the login page to reappear** before doing anything else.
|
||||
|
||||
## Step 6 — Verify
|
||||
|
||||
After restart, log back in and check:
|
||||
**Manage Jenkins** → **Plugins** → **Installed plugins** tab → search `Git` and `GitLab` — both should be listed.
|
||||
|
||||
## Task 70
|
||||
|
||||
The Nautilus team is integrating Jenkins into their CI/CD pipelines. After setting up a new Jenkins server, they're now configuring user access for the development team, Follow these steps:
|
||||
|
||||
1. Click on the Jenkins button on the top bar to access the Jenkins UI. Login with username admin and password Adm!n321.
|
||||
2. Create a jenkins user named rose with the password TmPcZjtRQx. Their full name should match Rose.
|
||||
3. Utilize the Project-based Matrix Authorization Strategy to assign overall read permission to the rose user.
|
||||
4. Remove all permissions for Anonymous users (if any) ensuring that the admin user retains overall Administer permissions.
|
||||
5. For the existing job, grant rose user only read permissions, disregarding other permissions such as Agent, SCM etc.
|
||||
|
||||
|
||||
Note:
|
||||
1. You may need to install plugins and restart Jenkins service. After plugins installation, select Restart Jenkins when installation is complete and no jobs are running on plugin installation/update page.
|
||||
2. After restarting the Jenkins service, wait for the Jenkins login page to reappear before proceeding. Avoid clicking Finish immediately after restarting the service.
|
||||
3. Capture screenshots of your configuration for review purposes. Consider using screen recording software like loom.com for documentation and sharing.
|
||||
|
||||
### Solution:
|
||||
|
||||
# Configure Jenkins user access for rose
|
||||
|
||||
## Step 1 — Log in
|
||||
Click **Jenkins** button → login `admin` / `Adm!n321`.
|
||||
|
||||
## Step 2 — Install Matrix Authorization Strategy plugin (if not present)
|
||||
**Manage Jenkins → Plugins → Available plugins** → search **"Matrix Authorization Strategy"** → check it → **Download now and install after restart**.
|
||||
|
||||
Wait for Jenkins to fully restart, wait for the **login page** to reappear, log back in.
|
||||
|
||||
## Step 3 — Create user rose
|
||||
**Manage Jenkins → Users → Create User**:
|
||||
- Username: `rose`
|
||||
- Password: `TmPcZjtRQx`
|
||||
- Full name: `Rose`
|
||||
- (email can be left blank or any value)
|
||||
|
||||
Click **Create User**.
|
||||
|
||||
## Step 4 — Switch to Project-based Matrix Authorization Strategy
|
||||
**Manage Jenkins → Security** (or **Configure Global Security**):
|
||||
- Under **Authorization**, select **"Project-based Matrix Authorization Strategy"**
|
||||
- A permission matrix appears.
|
||||
|
||||
## Step 5 — Set global permissions
|
||||
In the matrix:
|
||||
- **Add user** `admin` → grant **Overall / Administer** (full row, or at minimum Administer). **Do this FIRST — critical.**
|
||||
- **Add user** `rose` → grant **Overall / Read** only.
|
||||
- **Anonymous** row → **uncheck ALL permissions** (remove everything).
|
||||
|
||||
Click **Save**.
|
||||
|
||||
## Step 6 — Grant rose read-only on the existing job
|
||||
Go to the existing job → **Configure** → enable **"Enable project-based security"** (checkbox in the job config):
|
||||
- **Add user** `rose`
|
||||
- Grant **only Read** (under Job → Read). Leave Agent, SCM, Build, etc. **unchecked**.
|
||||
|
||||
Click **Save**.
|
||||
|
||||
## Verify
|
||||
- `admin` retains Administer (you can still access everything).
|
||||
- `rose` can log in, sees the job (read), can't configure/build.
|
||||
- Anonymous has no permissions.
|
||||
927
100 - days of devops/devops-71-80.md
Normal file
927
100 - days of devops/devops-71-80.md
Normal file
@@ -0,0 +1,927 @@
|
||||
## Task 71
|
||||
|
||||
Some new requirements have come up to install and configure some packages on the Nautilus infrastructure under Stratos Datacenter. The Nautilus DevOps team installed and configured a new Jenkins server so they wanted to create a Jenkins job to automate this task. Find below more details and complete the task accordingly:
|
||||
|
||||
1. Access the Jenkins UI by clicking on the Jenkins button in the top bar. Log in using the credentials: username admin and password Adm!n321.
|
||||
2. Create a new Jenkins job named install-packages and configure it with the following specifications:
|
||||
|
||||
Add a string parameter named PACKAGE.
|
||||
|
||||
Configure the job to install a package specified in the $PACKAGE parameter on the storage server (Stratos Datacenter).
|
||||
|
||||
Build the job at least once (e.g. with parameter PACKAGE=vim-enhanced) so the package is installed on the Storage server and can be verified.
|
||||
|
||||
### Solution
|
||||
|
||||
# Jenkins job: install-packages
|
||||
|
||||
## Step 1 — Log in
|
||||
Click **Jenkins** → login `admin` / `Adm!n321`.
|
||||
|
||||
## Step 2 — Install the "SSH" plugin (Publish Over SSH)
|
||||
**Manage Jenkins → Plugins → Available plugins** → search **"SSH"** (the "Publish Over SSH" plugin) → check → **Download now and install after restart**.
|
||||
|
||||
Wait for restart, wait for login page, log back in.
|
||||
|
||||
## Step 3 — Configure the SSH remote host (storage server)
|
||||
**Manage Jenkins → System** (Configure System) → scroll to **Publish over SSH** section → **SSH Servers → Add**:
|
||||
- **Name**: `ststor01` (a label)
|
||||
- **Hostname**: `ststor01` (or its IP)
|
||||
- **Username**: `natasha`
|
||||
- Click **Advanced** → check **Use password authentication** → **Passphrase/Password**: `Bl@kW`
|
||||
|
||||
Click **Test Configuration** — should say **Success**. Save.
|
||||
|
||||
## Step 4 — Create the job
|
||||
**New Item** → name `install-packages` → **Freestyle project** → OK.
|
||||
|
||||
## Step 5 — Add the string parameter
|
||||
In job config → check **"This project is parameterized"** → **Add Parameter → String Parameter**:
|
||||
- **Name**: `PACKAGE`
|
||||
- (default value optional, e.g. `vim-enhanced`)
|
||||
|
||||
## Step 6 — Add the build step (install on storage server)
|
||||
Under **Build Steps** → **Add build step → Send files or execute commands over SSH**:
|
||||
- **SSH Server**: select `ststor01`
|
||||
- **Exec command**:
|
||||
```bash
|
||||
sudo yum install -y $PACKAGE
|
||||
```
|
||||
|
||||
Save.
|
||||
|
||||
## Step 7 — Build with a parameter
|
||||
Click **Build with Parameters** → set `PACKAGE` = `vim-enhanced` → **Build**.
|
||||
|
||||
Check the build's **Console Output** — should show the yum install succeeding.
|
||||
|
||||
## Verify
|
||||
On ststor01:
|
||||
```bash
|
||||
rpm -q vim-enhanced # should show it's installed
|
||||
```
|
||||
|
||||
|
||||
## Task 72
|
||||
|
||||
A new DevOps Engineer has joined the team and he will be assigned some Jenkins related tasks. Before that, the team wanted to test a simple parameterized job to understand basic functionality of parameterized builds. He is given a simple parameterized job to build in Jenkins. Please find more details below:
|
||||
|
||||
Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and password Adm!n321.
|
||||
|
||||
1. Create a parameterized job which should be named as parameterized-job
|
||||
2. Add a string parameter named Stage; its default value should be Build.
|
||||
3. Add a choice parameter named env; its choices should be Development, Staging and Production.
|
||||
4. Configure job to execute a shell command, which should echo both parameter values (you are passing in the job).
|
||||
5. Build the Jenkins job at least once with choice parameter value Development to make sure it passes.
|
||||
|
||||
### solution
|
||||
|
||||
# Jenkins job: parameterized-job
|
||||
|
||||
## Step 1 — Log in
|
||||
Click **Jenkins** → login `admin` / `Adm!n321`.
|
||||
|
||||
## Step 2 — Create the job
|
||||
**New Item** → name `parameterized-job` → **Freestyle project** → OK.
|
||||
|
||||
## Step 3 — Enable parameters
|
||||
In job config → check **"This project is parameterized"**.
|
||||
|
||||
### String parameter
|
||||
**Add Parameter → String Parameter**:
|
||||
- **Name**: `Stage`
|
||||
- **Default Value**: `Build`
|
||||
|
||||
### Choice parameter
|
||||
**Add Parameter → Choice Parameter**:
|
||||
- **Name**: `env`
|
||||
- **Choices** (one per line):
|
||||
```
|
||||
Development
|
||||
Staging
|
||||
Production
|
||||
```
|
||||
|
||||
## Step 4 — Add the shell build step
|
||||
Under **Build Steps** → **Add build step → Execute shell**:
|
||||
```bash
|
||||
echo "Stage: $Stage"
|
||||
echo "env: $env"
|
||||
```
|
||||
|
||||
Save.
|
||||
|
||||
## Step 5 — Build with parameters
|
||||
Click **Build with Parameters**:
|
||||
- `Stage` = `Build` (default)
|
||||
- `env` = `Development`
|
||||
|
||||
Click **Build**.
|
||||
|
||||
## Verify
|
||||
Open the build → **Console Output** → should show:
|
||||
```
|
||||
Stage: Build
|
||||
env: Development
|
||||
```
|
||||
Build result: **SUCCESS**.
|
||||
|
||||
## Task 73
|
||||
|
||||
The devops team of xFusionCorp Industries is working on to setup centralised logging management system to maintain and analyse server logs easily. Since it will take some time to implement, they wanted to gather some server logs on a regular basis. At least one of the app servers is having issues with the Apache server. The team needs Apache logs so that they can identify and troubleshoot the issues easily if they arise. So they decided to create a Jenkins job to collect logs from the server. Please create/configure a Jenkins job as per details mentioned below:
|
||||
|
||||
Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and password Adm!n321
|
||||
|
||||
1. Create a Jenkins jobs named copy-logs.
|
||||
2. Configure it to periodically build every 5 minutes to copy the Apache logs (both access_log and error_log) from App Server 2 (stapp02) from the default logs location to location /usr/src/itadmin on the Storage Server.
|
||||
3. Build the job at least once so that the logs are copied and can be verified.
|
||||
|
||||
### Solution
|
||||
|
||||
# Jenkins job: copy-logs
|
||||
|
||||
## Step 1 — Log in
|
||||
Click **Jenkins** → login `admin` / `Adm!n321`.
|
||||
|
||||
## Step 2 — Ensure "Publish over SSH" plugin is installed
|
||||
**Manage Jenkins → Plugins → Available** → search **"SSH"** (Publish Over SSH) → install if not present → restart, wait for login.
|
||||
|
||||
## Step 3 — Configure SSH server for App Server 2
|
||||
**Manage Jenkins → System → Publish over SSH → SSH Servers → Add**:
|
||||
- **Name**: `stapp02`
|
||||
- **Hostname**: `stapp02`
|
||||
- **Username**: `steve`
|
||||
- **Advanced** → **Use password authentication** → Password: `Am3ric@`
|
||||
- **Test Configuration** → should succeed. Save.
|
||||
|
||||
## Step 4 — Set up passwordless SSH from stapp02 → ststor01
|
||||
The job will run *on stapp02* and scp to ststor01, so stapp02's steve needs passwordless access to ststor01's natasha. SSH into stapp02 and set it up:
|
||||
|
||||
```bash
|
||||
ssh steve@stapp02
|
||||
ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa # if not present
|
||||
ssh-copy-id natasha@ststor01 # password: Bl@kW
|
||||
# test:
|
||||
ssh natasha@ststor01 hostname # should return ststor01, no prompt
|
||||
```
|
||||
|
||||
## Step 5 — Create the job
|
||||
**New Item** → name `copy-logs` → **Freestyle project** → OK.
|
||||
|
||||
## Step 6 — Configure the schedule (every 5 min)
|
||||
Check **Build Triggers → Build periodically** → Schedule:
|
||||
```
|
||||
*/5 * * * *
|
||||
```
|
||||
|
||||
## Step 7 — Add the build step
|
||||
**Build Steps → Send files or execute commands over SSH**:
|
||||
- **SSH Server**: `stapp02`
|
||||
- **Exec command**:
|
||||
```bash
|
||||
scp /var/log/httpd/access_log natasha@ststor01:/usr/src/itadmin/
|
||||
scp /var/log/httpd/error_log natasha@ststor01:/usr/src/itadmin/
|
||||
```
|
||||
|
||||
Save.
|
||||
|
||||
## Step 8 — Build once
|
||||
Click **Build Now**. Check **Console Output** for success.
|
||||
|
||||
## Verify
|
||||
On ststor01:
|
||||
```bash
|
||||
ls -l /usr/src/itadmin/
|
||||
# should show access_log and error_log
|
||||
```
|
||||
|
||||
## Task 74
|
||||
|
||||
There is a requirement to create a Jenkins job to automate the database backup. Below you can find more details to accomplish this task:
|
||||
|
||||
Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and password Adm!n321.
|
||||
|
||||
Create a Jenkins job named database-backup.
|
||||
|
||||
Configure it to take a database dump of the kodekloud_db01 database present on the App server (stapp01) in Stratos Datacenter, the database user is kodekloud_roy and password is asdfgdsd.
|
||||
|
||||
The dump should be named in db_$(date +%F).sql format, where date +%F is the current date.
|
||||
Copy the db_$(date +%F).sql dump to the Storage server (ststor01) under location /home/natasha/db_backups.
|
||||
|
||||
Further, schedule this job to run periodically at */10 * * * * (please use this exact schedule format).
|
||||
|
||||
### Schedule
|
||||
|
||||
# Jenkins job: database-backup
|
||||
|
||||
## Step 1 — Log in
|
||||
Click **Jenkins** → login `admin` / `Adm!n321`.
|
||||
|
||||
## Step 2 — Ensure "Publish over SSH" plugin installed
|
||||
**Manage Jenkins → Plugins → Available** → search **"SSH"** (Publish Over SSH) → install if needed → restart, wait for login.
|
||||
|
||||
## Step 3 — Configure SSH server for App Server 1
|
||||
**Manage Jenkins → System → Publish over SSH → SSH Servers → Add**:
|
||||
- **Name**: `stapp01`
|
||||
- **Hostname**: `stapp01`
|
||||
- **Username**: `tony`
|
||||
- **Advanced** → **Use password authentication** → Password: `Ir0nM@n`
|
||||
- **Test Configuration** → success. Save.
|
||||
|
||||
## Step 4 — Passwordless SSH from stapp01 → ststor01
|
||||
The dump runs on stapp01 and scp's to ststor01, so tony@stapp01 needs passwordless access to natasha@ststor01:
|
||||
|
||||
```bash
|
||||
ssh tony@stapp01
|
||||
ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa # if not present
|
||||
ssh-copy-id natasha@ststor01 # password: Bl@kW
|
||||
ssh natasha@ststor01 hostname # test — should return ststor01, no prompt
|
||||
```
|
||||
|
||||
## Step 5 — Create the job
|
||||
**New Item** → name `database-backup` → **Freestyle project** → OK.
|
||||
|
||||
## Step 6 — Schedule
|
||||
**Build Triggers → Build periodically** → Schedule:
|
||||
```
|
||||
*/10 * * * *
|
||||
```
|
||||
|
||||
## Step 7 — Build step
|
||||
**Build Steps → Send files or execute commands over SSH**:
|
||||
- **SSH Server**: `stapp01`
|
||||
- **Exec command**:
|
||||
```bash
|
||||
mysqldump -u kodekloud_roy -pasdfgdsd kodekloud_db01 > /tmp/db_$(date +%F).sql
|
||||
scp /tmp/db_$(date +%F).sql natasha@ststor01:/home/natasha/db_backups/
|
||||
```
|
||||
|
||||
Save.
|
||||
|
||||
## Step 8 — Build once
|
||||
Click **Build Now** → check **Console Output** for success.
|
||||
|
||||
## Verify
|
||||
On ststor01:
|
||||
```bash
|
||||
ls -l /home/natasha/db_backups/
|
||||
# should show db_YYYY-MM-DD.sql
|
||||
```
|
||||
|
||||
## Task 75
|
||||
|
||||
The Nautilus DevOps team has installed and configured new Jenkins server in Stratos DC which they will use for CI/CD and for some automation tasks. There is a requirement to add all app servers as slave nodes in Jenkins so that they can perform tasks on these servers using Jenkins. Find below more details and accomplish the task accordingly.
|
||||
|
||||
Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and password Adm!n321.
|
||||
|
||||
1. Add all app servers as SSH build agent/slave nodes in Jenkins. Slave node name for app server 1, app server 2 and app server 3 must be App_server_1, App_server_2, App_server_3 respectively.
|
||||
|
||||
2. Add labels as below:
|
||||
|
||||
App_server_1 : stapp01
|
||||
App_server_2 : stapp02
|
||||
App_server_3 : stapp03
|
||||
|
||||
3. Remote root directory for App_server_1 must be /home/tony/jenkins, for App_server_2 must be /home/steve/jenkins and for App_server_3 must be /home/banner/jenkins.
|
||||
|
||||
|
||||
### Solution
|
||||
|
||||
# Add app servers as Jenkins SSH build agents
|
||||
|
||||
## Step 1 — Log in
|
||||
Click **Jenkins** → login `admin` / `Adm!n321`.
|
||||
|
||||
## Step 2 — Ensure "SSH Build Agents" plugin is installed
|
||||
**Manage Jenkins → Plugins → Available** → search **"SSH Build Agents"** → install if needed → restart, wait for login.
|
||||
|
||||
## Step 3 — Prerequisite: Java + remote dirs on each app server
|
||||
Agents run a Java process, and the remote root dir must exist. On each app server:
|
||||
|
||||
```bash
|
||||
# App Server 1 (tony)
|
||||
ssh tony@stapp01
|
||||
# Ir0nM@n
|
||||
sudo yum install -y java-17-openjdk # or java-17; match Jenkins controller
|
||||
mkdir -p /home/tony/jenkins
|
||||
exit
|
||||
|
||||
# App Server 2 (steve)
|
||||
ssh steve@stapp02
|
||||
# Am3ric@
|
||||
sudo yum install -y java-17-openjdk
|
||||
mkdir -p /home/steve/jenkins
|
||||
exit
|
||||
|
||||
# App Server 3 (banner)
|
||||
ssh banner@stapp03
|
||||
# BigGr33n
|
||||
sudo yum install -y java-21-openjdk
|
||||
mkdir -p /home/banner/jenkins
|
||||
exit
|
||||
```
|
||||
|
||||
## Step 4 — Add credentials for each app server
|
||||
**Manage Jenkins → Credentials → System → Global credentials → Add Credentials**:
|
||||
|
||||
For each app server, add a **Username with password** credential:
|
||||
| Server | Username | Password |
|
||||
|--------|----------|----------|
|
||||
| stapp01 | tony | Ir0nM@n |
|
||||
| stapp02 | steve | Am3ric@ |
|
||||
| stapp03 | banner | BigGr33n |
|
||||
|
||||
(Kind: Username with password. Set a recognizable ID like `stapp01-cred`.)
|
||||
|
||||
## Step 5 — Create the three nodes
|
||||
|
||||
For **App_server_1**:
|
||||
**Manage Jenkins → Nodes → New Node**:
|
||||
- **Node name**: `App_server_1`
|
||||
- Type: **Permanent Agent** → Create
|
||||
- **Remote root directory**: `/home/tony/jenkins`
|
||||
- **Labels**: `stapp01`
|
||||
- **Usage**: Use this node as much as possible
|
||||
- **Launch method**: **Launch agents via SSH**
|
||||
- **Host**: `stapp01`
|
||||
- **Credentials**: select the tony credential
|
||||
- **Host Key Verification Strategy**: **Non verifying Verification Strategy** (or Manually trusted)
|
||||
- Save.
|
||||
|
||||
Repeat for **App_server_2**:
|
||||
- Node name: `App_server_2`
|
||||
- Remote root directory: `/home/steve/jenkins`
|
||||
- Labels: `stapp02`
|
||||
- SSH Host: `stapp02`, credentials: steve
|
||||
|
||||
Repeat for **App_server_3**:
|
||||
- Node name: `App_server_3`
|
||||
- Remote root directory: `/home/banner/jenkins`
|
||||
- Labels: `stapp03`
|
||||
- SSH Host: `stapp03`, credentials: banner
|
||||
|
||||
## Step 6 — Verify
|
||||
**Manage Jenkins → Nodes** — all three (App_server_1/2/3) should show **online** (green, no red X). Click each; the agent log should show a successful connection.
|
||||
|
||||
## Task 76
|
||||
|
||||
The xFusionCorp Industries has recruited some new developers. There are already some existing jobs on Jenkins and two of these new developers need permissions to access those jobs. The development team has already shared those requirements with the DevOps team, so as per details mentioned below grant required permissions to the developers.
|
||||
|
||||
Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and password Adm!n321.
|
||||
|
||||
There is an existing Jenkins job named Packages, there are also two existing Jenkins users named sam with password sam@pass12345 and rohan with password rohan@pass12345.
|
||||
|
||||
Grant permissions to these users to access Packages job as per details mentioned below:
|
||||
|
||||
a.) Make sure to select Inherit permissions from parent ACL under inheritance strategy for granting permissions to these users.
|
||||
b.) Grant mentioned permissions to sam user : build, configure and read.
|
||||
c.) Grant mentioned permissions to rohan user : build, cancel, configure, read, update and tag.
|
||||
|
||||
Note:
|
||||
Please do not modify/alter any other existing job configuration.
|
||||
|
||||
You might need to install some plugins and restart Jenkins service. So, we recommend clicking on Restart Jenkins when installation is complete and no jobs are running on plugin installation/update page i.e update centre. Also Jenkins UI sometimes gets stuck when Jenkins service restarts in the back end. In this case, please make sure to refresh the UI page.
|
||||
|
||||
For these kind of scenarios requiring changes to be done in a web UI, please take screenshots so that you can share it with us for review in case your task is marked incomplete. You may also consider using a screen recording software such as loom.com to record and share your work.
|
||||
|
||||
### Solution
|
||||
|
||||
# Grant sam + rohan permissions on Packages job
|
||||
|
||||
## Step 1 — Log in
|
||||
Click **Jenkins** → login `admin` / `Adm!n321`.
|
||||
|
||||
## Step 2 — Confirm global authorization supports project-based matrix
|
||||
2. (If Matrix Auth plugin missing) install it → "Restart when complete & no jobs running" → refresh UI if stuck → wait for login page.
|
||||
3. Ensure global Authorization = Project-based Matrix Auth (don't change if already set; ensure admin keeps Administer).
|
||||
|
||||
|
||||
**Manage Jenkins → Security** → Authorization should be **"Project-based Matrix Authorization Strategy"**. (If it's already set from a prior task, leave it — just ensure admin has Overall/Administer so you don't lock out.)
|
||||
|
||||
## Step 3 — Open the Packages job's security config
|
||||
Go to the **Packages** job → **Configure** → find **"Enable project-based security"** and check it.
|
||||
|
||||
Set **Inheritance Strategy** → **"Inherit permissions from parent ACL"**.
|
||||
|
||||
## Step 4 — Add sam and grant permissions
|
||||
Click **Add user** → enter `sam` → check these boxes for sam:
|
||||
- **Job → Build**
|
||||
- **Job → Configure**
|
||||
- **Job → Read**
|
||||
|
||||
## Step 5 — Add rohan and grant permissions
|
||||
Click **Add user** → enter `rohan` → check these for rohan:
|
||||
- **Job → Build**
|
||||
- **Job → Cancel** (under Run category, "Cancel")
|
||||
- **Job → Configure**
|
||||
- **Job → Read**
|
||||
- **Job → Update** (SCM/Update — the "Update" permission)
|
||||
- **Job → Tag** (SCM → Tag)
|
||||
|
||||
## Step 6 — Save
|
||||
Click **Save**.
|
||||
|
||||
## Verify
|
||||
- Log in as `sam` → can see/build/configure Packages, nothing more.
|
||||
- Log in as `rohan` → can see/build/cancel/configure/update/tag Packages.
|
||||
|
||||
## Task 77
|
||||
|
||||
The development team of xFusionCorp Industries is working on to develop a new static website and they are planning to deploy the same on Nautilus App Server using Jenkins pipeline. They have shared their requirements with the DevOps team and accordingly we need to create a Jenkins pipeline job. Please find below more details about the task:
|
||||
|
||||
Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and password Adm!n321.
|
||||
|
||||
Similarly, click on the Gitea button on the top bar to access the Gitea UI. Login using username sarah and password Sarah_pass123. There under user sarah you will find a repository named web_app that is already cloned on App Server 1 under /var/www/html. sarah is a developer who is working on this repository.
|
||||
|
||||
Add a slave node named App Server 1. It should be labeled as stapp01 and its remote root directory should be /home/sarah/jenkins_agent (the repository is cloned under /var/www/html; the agent uses a separate directory so it does not pollute the repo).
|
||||
|
||||
We have already cloned repository on App Server 1 under /var/www/html.
|
||||
|
||||
Apache is already installed on the app server and is running on port 8080.
|
||||
|
||||
Create a Jenkins pipeline job named xfusion-webapp-job (it must not be a Multibranch pipeline) and configure it to:
|
||||
|
||||
Deploy the code from web_app repository under /var/www/html on App Server 1, as this is the document root of the app server. The pipeline should have a single stage named Deploy ( which is case sensitive ) to accomplish the deployment.
|
||||
|
||||
LB server is already configured. You should be able to see the latest changes you made by clicking on the App button. Please make sure the required content is loading on the main URL https://<LBR-URL> i.e there should not be a sub-directory like https://<LBR-URL>/web_app etc.
|
||||
|
||||
Note:
|
||||
You might need to install some plugins and restart Jenkins service. So, we recommend clicking on Restart Jenkins when installation is complete and no jobs are running on plugin installation/update page i.e update centre. Also, Jenkins UI sometimes gets stuck when Jenkins service restarts in the back end. In this case, please make sure to refresh the UI page.
|
||||
|
||||
For these kind of scenarios requiring changes to be done in a web UI, please take screenshots so that you can share it with us for review in case your task is marked incomplete. You may also consider using a screen recording software such as loom.com to record and share your work.
|
||||
|
||||
### Solution
|
||||
|
||||
#### Prerequisities
|
||||
|
||||
```bash
|
||||
ssh sarah@stapp01 # or via the appropriate user
|
||||
# Sarah_pass123
|
||||
# Java for the agent (match controller — likely 17)
|
||||
sudo yum install -y java-17-openjdk
|
||||
sudo alternatives --set java java-17-openjdk.x86_64 # make it default
|
||||
java -version
|
||||
|
||||
# agent working dir
|
||||
mkdir -p /home/sarah/jenkins_agent
|
||||
|
||||
# confirm the repo is there + git works in docroot
|
||||
cd /var/www/html
|
||||
sudo git status # confirm it's a git clone of web_app
|
||||
```
|
||||
|
||||
# Jenkins pipeline: xfusion-webapp-job
|
||||
|
||||
## Step 1 — Log in
|
||||
Jenkins UI → `admin` / `Adm!n321`.
|
||||
|
||||
## Step 2 — Install required plugins
|
||||
Likely needed: **SSH Build Agents**, **Pipeline**, **Git**.
|
||||
**Manage Jenkins → Plugins → Available** → install any missing → **"Restart Jenkins when installation is complete and no jobs are running"** → refresh UI if it hangs → wait for login page.
|
||||
|
||||
## Step 3 — Add credentials for sarah@stapp01
|
||||
**Manage Jenkins → Credentials → Global → Add Credentials**:
|
||||
- Kind: Username with password
|
||||
- Username: `sarah`
|
||||
- Password: (sarah's app-server password — check the creds table)
|
||||
- ID: `stapp01-sarah`
|
||||
|
||||
## Step 4 — Add the agent node
|
||||
**Manage Jenkins → Nodes → New Node**:
|
||||
- **Name**: `App Server 1` (exact — with spaces)
|
||||
- Type: Permanent Agent
|
||||
- **Remote root directory**: `/home/sarah/jenkins_agent`
|
||||
- **Labels**: `stapp01`
|
||||
- **Launch method**: Launch agents via SSH
|
||||
- Host: `stapp01`
|
||||
- Credentials: `stapp01-sarah`
|
||||
- Host Key Verification Strategy: **Non verifying**
|
||||
- Save → confirm it comes **online**.
|
||||
|
||||
## Step 5 — Create the pipeline job
|
||||
**New Item** → name `xfusion-webapp-job` → **Pipeline** (NOT Multibranch) → OK.
|
||||
|
||||
In the job config → **Pipeline** section → Definition: **Pipeline script** → paste:
|
||||
|
||||
```groovy
|
||||
pipeline {
|
||||
agent { label 'stapp01' }
|
||||
stages {
|
||||
stage('Deploy') {
|
||||
steps {
|
||||
sh '''
|
||||
cd /var/www/html
|
||||
git pull origin master
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Save.
|
||||
|
||||
## Step 6 — Build
|
||||
**Build Now** → check Console Output for success.
|
||||
|
||||
## Step 7 — Verify
|
||||
- App button / `https://<LBR-URL>` shows the web_app content at the **root** (no /web_app subdir).
|
||||
- On stapp01: `ls /var/www/html` shows the latest repo files.
|
||||
|
||||
## Task 78
|
||||
|
||||
The development team of xFusionCorp Industries is working on to develop a new static website and they are planning to deploy the same on Nautilus App Server using Jenkins pipeline. They have shared their requirements with the DevOps team and accordingly we need to create a Jenkins pipeline job. Please find below more details about the task:
|
||||
|
||||
Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and password Adm!n321.
|
||||
|
||||
Similarly, click on the Gitea button on the top bar to access the Gitea UI. Login using username sarah and password Sarah_pass123. There under user sarah you will find a repository named web_app that is already cloned on App Server 1 under /var/www/html. sarah is a developer who is working on this repository.
|
||||
|
||||
Add a slave node named App Server 1. It should be labeled as stapp01 and its remote root directory should be /home/sarah/jenkins_agent (the repository is cloned under /var/www/html).
|
||||
|
||||
We have already cloned repository on App Server 1 under /var/www/html.
|
||||
|
||||
Apache is already installed on the app server and is running on port 8080.
|
||||
|
||||
Create a Jenkins pipeline job named nautilus-webapp-job (it must not be a Multibranch pipeline) and configure it to:
|
||||
|
||||
Add a string parameter named BRANCH.
|
||||
|
||||
It should conditionally deploy the code from web_app repository under /var/www/html on App Server 1, as this is the document root of the app server. The pipeline should have a single stage named Deploy ( which is case sensitive ) to accomplish the deployment.
|
||||
|
||||
The pipeline should be conditional, if the value master is passed to the BRANCH parameter then it must deploy the master branch, on the other hand if the value feature is passed to the BRANCH parameter then it must deploy the feature branch.
|
||||
|
||||
LB server is already configured. You should be able to see the latest changes you made by clicking on the App button. Please make sure the required content is loading on the main URL https://<LBR-URL> i.e there should not be a sub-directory like https://<LBR-URL>/web_app etc.
|
||||
|
||||
Note:
|
||||
You might need to install some plugins and restart Jenkins service. So, we recommend clicking on Restart Jenkins when installation is complete and no jobs are running on plugin installation/update page i.e update centre. Also, Jenkins UI sometimes gets stuck when Jenkins service restarts in the back end. In this case, please make sure to refresh the UI page.
|
||||
|
||||
For these kind of scenarios requiring changes to be done in a web UI, please take screenshots so that you can share it with us for review in case your task is marked incomplete. You may also consider using a screen recording software such as loom.com to record and share your work.
|
||||
|
||||
|
||||
### Solution
|
||||
|
||||
#### Prerequisities
|
||||
|
||||
```bash
|
||||
ssh sarah@stapp01 # or via the appropriate user
|
||||
# Sarah_pass123
|
||||
# Java for the agent (match controller — likely 17)
|
||||
sudo yum install -y java-17-openjdk
|
||||
sudo alternatives --set java java-17-openjdk.x86_64 # make it default
|
||||
java -version
|
||||
|
||||
# agent working dir
|
||||
mkdir -p /home/sarah/jenkins_agent
|
||||
|
||||
# confirm the repo is there + git works in docroot
|
||||
cd /var/www/html
|
||||
sudo git status # confirm it's a git clone of web_app
|
||||
```
|
||||
|
||||
## Step 2 — Install required plugins
|
||||
Likely needed: **SSH Build Agents**, **Pipeline**, **Git**.
|
||||
**Manage Jenkins → Plugins → Available** → install any missing → **"Restart Jenkins when installation is complete and no jobs are running"** → refresh UI if it hangs → wait for login page.
|
||||
|
||||
## Step 3 — Add credentials for sarah@stapp01
|
||||
**Manage Jenkins → Credentials → Global → Add Credentials**:
|
||||
- Kind: Username with password
|
||||
- Username: `sarah`
|
||||
- Password: (sarah's app-server password — check the creds table)
|
||||
- ID: `stapp01-sarah`
|
||||
|
||||
## Step 4 — Add the agent node
|
||||
**Manage Jenkins → Nodes → New Node**:
|
||||
- **Name**: `App Server 1` (exact — with spaces)
|
||||
- Type: Permanent Agent
|
||||
- **Remote root directory**: `/home/sarah/jenkins_agent`
|
||||
- **Labels**: `stapp01`
|
||||
- **Launch method**: Launch agents via SSH
|
||||
- Host: `stapp01`
|
||||
- Credentials: `stapp01-sarah`
|
||||
- Host Key Verification Strategy: **Non verifying**
|
||||
- Save → confirm it comes **online**.
|
||||
|
||||
|
||||
|
||||
# Jenkins pipeline: nautilus-webapp-job (conditional)
|
||||
|
||||
## Create the pipeline job
|
||||
**New Item** → name `nautilus-webapp-job` → **Pipeline** (NOT Multibranch) → OK.
|
||||
|
||||
## Add the string parameter
|
||||
Check **"This project is parameterized"** → **Add Parameter → String Parameter**:
|
||||
- **Name**: `BRANCH`
|
||||
|
||||
## Pipeline script
|
||||
**Pipeline** section → Definition: **Pipeline script** → paste:
|
||||
|
||||
```groovy
|
||||
pipeline {
|
||||
agent { label 'stapp01' }
|
||||
stages {
|
||||
stage('Deploy') {
|
||||
steps {
|
||||
script {
|
||||
if (params.BRANCH == 'master') {
|
||||
sh '''
|
||||
cd /var/www/html
|
||||
git checkout master
|
||||
git pull origin master
|
||||
'''
|
||||
} else if (params.BRANCH == 'feature') {
|
||||
sh '''
|
||||
cd /var/www/html
|
||||
git checkout feature
|
||||
git pull origin feature
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Save.
|
||||
|
||||
## Build
|
||||
**Build with Parameters** → set `BRANCH` = `master` (then test again with `feature`) → Build.
|
||||
|
||||
## Verify
|
||||
- App button / `https://<LBR-URL>` shows the deployed branch's content at root.
|
||||
- On stapp01: `cd /var/www/html && git branch` shows the checked-out branch.
|
||||
|
||||
## Task 79
|
||||
|
||||
The Nautilus development team had a meeting with the DevOps team where they discussed automating the deployment of one of their apps using Jenkins (the one in Stratos Datacenter). They want to auto deploy the new changes in case any developer pushes to the repository. As per the requirements mentioned below configure the required Jenkins job.
|
||||
|
||||
Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and Adm!n321 password.
|
||||
|
||||
Similarly, you can access the Gitea UI using Gitea button. Username and password for Git are sarah and Sarah_pass123. Under user sarah you will find a repository named web that is already cloned on App Server 1 under sarah's home (/home/sarah/web). sarah is a developer who is working on this repository.
|
||||
|
||||
1. httpd is already installed and configured on the app server (listening on port 8080). Ensure the httpd service is running on App Server 1 (e.g. start it manually if needed). You can make starting/restarting httpd part of your Jenkins job if you prefer.
|
||||
|
||||
2. Create a Jenkins job named datacenter-app-deployment and configure it so that if anyone pushes any new change to the origin repository in master branch, the job should auto build and deploy the latest code on App Server 1 under /var/www/html directory.
|
||||
Before deployment, ensure that the ownership of the /var/www/html directory is set to user sarah, so that Jenkins can successfully deploy files to that directory.
|
||||
|
||||
3. SSH into App Server 1 using sarah user credentials mentioned above. Under sarah user's home (/home/sarah/web) you will find a cloned Git repository named web. Under this repository there is an index.html file, update its content to Welcome to the xFusionCorp Industries, then push the changes to the origin into master branch. This push must trigger your Jenkins job and the latest changes must be deployed on the server, also make sure it deploys the entire repository content not only index.html file.
|
||||
|
||||
Click on the App button on the top bar to access the app. Please make sure the required content is loading on the main URL (e.g. http://stlb01:8091) i.e there should not be any sub-directory like http://stlb01:8091/web etc.
|
||||
|
||||
Note:
|
||||
1. You might need to install some plugins and restart Jenkins service. So, we recommend clicking on Restart Jenkins when installation is complete and no jobs are running on plugin installation/update page i.e update centre. Also some times Jenkins UI gets stuck when Jenkins service restarts in the back end so in such case please make sure to refresh the UI page.
|
||||
2. Make sure Jenkins job passes even on repetitive runs as validation may try to build the job multiple times.
|
||||
3. Deployment related tasks should be done by sudo user on the destination server to avoid any permission issues so make sure to configure your Jenkins job accordingly.
|
||||
4. For these kind of scenarios requiring changes to be done in a web UI, please take screenshots so that you can share it with us for review in case your task is marked incomplete. You may also consider using a screen recording software such as loom.com to record and share your work.
|
||||
|
||||
### Solution
|
||||
|
||||
#### Prerequisities
|
||||
|
||||
```bash
|
||||
ssh sarah@stapp01 # or via the appropriate user
|
||||
# Sarah_pass123
|
||||
# Java for the agent (match controller — likely 17)
|
||||
sudo yum install -y java-17-openjdk
|
||||
sudo alternatives --set java java-17-openjdk.x86_64 # make it default
|
||||
java -version
|
||||
|
||||
# agent working dir
|
||||
mkdir -p /home/sarah/jenkins_agent
|
||||
|
||||
# confirm the repo clone
|
||||
cd /home/sarah/web && git remote -v && git branch
|
||||
```
|
||||
|
||||
# Jenkins job: datacenter-app-deployment (webhook auto-deploy)
|
||||
|
||||
## Step 1 — Plugins
|
||||
Ensure installed: **Git**, **Gitea** (or Generic Webhook Trigger), **Publish Over SSH** or the agent approach.
|
||||
Install missing → restart → refresh UI if stuck.
|
||||
|
||||
## Step 2 — Node or SSH config for stapp01
|
||||
Either use an SSH agent node (label stapp01) OR Publish-Over-SSH to stapp01.
|
||||
(Given note #3 "use sudo on destination," the job runs commands on stapp01 as sarah with sudo.)
|
||||
|
||||
## Step 3 — Create the job
|
||||
**New Item** → `datacenter-app-deployment` → **Freestyle project** → OK.
|
||||
|
||||
## Step 4 — Source Code Management
|
||||
**Git**:
|
||||
- Repository URL: the Gitea `web` repo URL (e.g. `http://<gitea>/sarah/web.git`)
|
||||
- Credentials: sarah / Sarah_pass123
|
||||
- Branch: `*/master`
|
||||
|
||||
## Step 5 — Build Trigger
|
||||
Check **"Poll SCM"** as a fallback AND configure the Gitea webhook trigger:
|
||||
- If Gitea plugin: check **"Build when a change is pushed to Gitea"**
|
||||
- (Fallback: Poll SCM with `* * * * *` or a webhook)
|
||||
|
||||
## Step 6 — Build step
|
||||
**Execute shell** (if job runs on stapp01 agent) — deploy the repo contents to docroot:
|
||||
```bash
|
||||
sudo cp -r $WORKSPACE/* /var/www/html/
|
||||
sudo systemctl restart httpd
|
||||
```
|
||||
Or if pulling directly on the server, sync the /home/sarah/web content.
|
||||
|
||||
## Step 7 — Configure Gitea webhook
|
||||
In Gitea → repo `web` → **Settings → Webhooks → Add Webhook → Gitea**:
|
||||
- Target URL: `http://<jenkins-url>/gitea-webhook/post` (or the Jenkins job's webhook endpoint)
|
||||
- Trigger: Push events
|
||||
- Save → Test Delivery.
|
||||
|
||||
## Step 8 — Make the change + push (proves the trigger)
|
||||
On stapp01 as sarah:
|
||||
```bash
|
||||
cd /home/sarah/web
|
||||
echo "Welcome to the xFusionCorp Industries" > index.html
|
||||
git add index.html
|
||||
git commit -m "Update index.html"
|
||||
git push origin master
|
||||
```
|
||||
This push should auto-trigger the Jenkins job.
|
||||
|
||||
## Verify
|
||||
- Jenkins job auto-built after the push.
|
||||
- `http://stlb01:8091` shows "Welcome to the xFusionCorp Industries" at root.
|
||||
- `/var/www/html` contains the full repo content, not just index.html.
|
||||
|
||||
|
||||
## Tak 80
|
||||
|
||||
The DevOps team was looking for a solution where they want to restart Apache service on all app servers if the deployment goes fine on these servers in Stratos Datacenter. After having a discussion, they came up with a solution to use Jenkins chained builds so that they can use a downstream job for services which should only be triggered by the deployment job. So as per the requirements mentioned below configure the required Jenkins jobs.
|
||||
|
||||
Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and Adm!n321 password.
|
||||
|
||||
Similarly you can access Gitea UI on port 3000 (or click the Gitea button) and username and password for Git is sarah and Sarah_pass123 respectively. Under user sarah you will find a repository named web.
|
||||
|
||||
Apache is already installed and configured on the app server. The doc root /var/www/html on App Server 1 is a local git repository tracking the origin web repository.
|
||||
|
||||
1. Create a Jenkins job named devops-app-deployment and configure it to pull changes from the master branch of the web repository on App Server 1 under /var/www/html directory.
|
||||
|
||||
2. Create another Jenkins job named manage-services and make it a downstream job for devops-app-deployment. Things to take care about this job are:
|
||||
|
||||
a. This job should restart httpd service on the app server (App Server 1).
|
||||
b. Trigger this job only if the upstream job i.e devops-app-deployment is stable.
|
||||
|
||||
The LB server is already configured. Click on the App button on the top bar to access the app. Please make sure the required content is loading on the main URL (e.g. http://stlb01:8091) i.e there should not be a sub-directory like http://stlb01:8091/web etc.
|
||||
|
||||
|
||||
Note:
|
||||
1. You might need to install some plugins and restart Jenkins service. So, we recommend clicking on Restart Jenkins when installation is complete and no jobs are running on plugin installation/update page i.e update centre. Also some times Jenkins UI gets stuck when Jenkins service restarts in the back end so in such case please make sure to refresh the UI page.
|
||||
2. Make sure Jenkins job passes even on repetitive runs as validation may try to build the job multiple times.
|
||||
3. Deployment related tasks should be done by sudo user on the destination server to avoid any permission issues so make sure to configure your Jenkins job accordingly.
|
||||
4. For these kind of scenarios requiring changes to be done in a web UI, please take screenshots so that you can share it with us for review in case your task is marked incomplete. You may also consider using a screen recording software such as loom.com to record and share your work.
|
||||
|
||||
### Solution
|
||||
|
||||
# Chained Jenkins Builds: devops-app-deployment → manage-services
|
||||
### Complete setup from an empty Jenkins
|
||||
|
||||
---
|
||||
|
||||
## PART 0 — Prerequisites on App Server 1 (CLI)
|
||||
|
||||
SSH into stapp01 and set up NOPASSWD sudo for sarah (deploy commands need passwordless sudo).
|
||||
|
||||
```bash
|
||||
ssh sarah@stapp01
|
||||
# password: Sarah_pass123
|
||||
|
||||
# NOPASSWD sudo drop-in
|
||||
echo 'sarah ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/sarah
|
||||
sudo chmod 0440 /etc/sudoers.d/sarah
|
||||
sudo visudo -c # must say "parsed OK"
|
||||
sudo -n true && echo "NOPASSWD works" # must print NOPASSWD works
|
||||
|
||||
# confirm docroot is a git repo tracking origin web
|
||||
cd /var/www/html
|
||||
sudo git remote -v # should show the 'web' origin
|
||||
sudo git branch # note the branch (master)
|
||||
|
||||
# ensure httpd is running
|
||||
sudo systemctl start httpd
|
||||
sudo systemctl status httpd --no-pager
|
||||
|
||||
exit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PART 1 — Install required plugins
|
||||
|
||||
Jenkins UI → login `admin` / `Adm!n321`.
|
||||
|
||||
**Manage Jenkins → Plugins → Available plugins**, search and install:
|
||||
- **Publish Over SSH**
|
||||
|
||||
Check **"Restart Jenkins when installation is complete and no jobs are running"**.
|
||||
Wait for the restart; if the UI hangs, **refresh the page**. Wait for the login page, then log back in.
|
||||
|
||||
---
|
||||
|
||||
## PART 2 — Configure the SSH server (stapp01)
|
||||
|
||||
**Manage Jenkins → System** (Configure System) → scroll to **Publish over SSH** → **SSH Servers → Add**:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Name | `stapp01` |
|
||||
| Hostname | `stapp01` |
|
||||
| Username | `sarah` |
|
||||
|
||||
Click **Advanced** → check **Use password authentication, or use a different key**:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Passphrase / Password | `Sarah_pass123` |
|
||||
|
||||
Click **Test Configuration** → must return **Success**. **Save**.
|
||||
|
||||
---
|
||||
|
||||
## PART 3 — Job 1: devops-app-deployment (the deploy)
|
||||
|
||||
**New Item** → name `devops-app-deployment` → **Freestyle project** → OK.
|
||||
|
||||
### Build step
|
||||
**Build Steps → Add build step → Send files or execute commands over SSH**:
|
||||
- **SSH Server**: `stapp01`
|
||||
- **Exec command**:
|
||||
```bash
|
||||
cd /var/www/html
|
||||
sudo git checkout master
|
||||
sudo git pull origin master
|
||||
```
|
||||
|
||||
**Save.**
|
||||
|
||||
---
|
||||
|
||||
## PART 4 — Job 2: manage-services (downstream service restart)
|
||||
|
||||
**New Item** → name `manage-services` → **Freestyle project** → OK.
|
||||
|
||||
### Build step
|
||||
**Build Steps → Add build step → Send files or execute commands over SSH**:
|
||||
- **SSH Server**: `stapp01`
|
||||
- **Exec command**:
|
||||
```bash
|
||||
sudo systemctl restart httpd
|
||||
```
|
||||
|
||||
**Save.**
|
||||
|
||||
---
|
||||
|
||||
## PART 5 — Chain the jobs (upstream → downstream, only if stable)
|
||||
|
||||
Open **devops-app-deployment → Configure**:
|
||||
|
||||
**Post-build Actions → Add post-build action → Build other projects**:
|
||||
- **Projects to build**: `manage-services`
|
||||
- Select **"Trigger only if build is stable"**
|
||||
|
||||
**Save.**
|
||||
|
||||
---
|
||||
|
||||
## PART 6 — Run and verify
|
||||
|
||||
### Build the upstream job
|
||||
Open **devops-app-deployment → Build Now**.
|
||||
|
||||
### Check the chain
|
||||
- `devops-app-deployment` completes **stable** (blue/green ball, SUCCESS).
|
||||
- It automatically triggers **manage-services** (visible in the build's "Downstream" section / console).
|
||||
- `manage-services` runs and restarts httpd.
|
||||
|
||||
### Verify on the servers
|
||||
```bash
|
||||
# on stapp01
|
||||
ssh sarah@stapp01 'cd /var/www/html && git log --oneline -3' # latest master pulled
|
||||
ssh sarah@stapp01 'systemctl is-active httpd' # active
|
||||
```
|
||||
|
||||
### Verify the app
|
||||
Click the **App** button → `http://stlb01:8091` must show the site content at the **root** (no `/web` subdirectory).
|
||||
|
||||
---
|
||||
|
||||
## Key points
|
||||
|
||||
- **Chaining is set on the UPSTREAM job** — "Build other projects" post-build action in `devops-app-deployment`, pointing at `manage-services`.
|
||||
- **"Trigger only if build is stable"** — `manage-services` runs ONLY when the deploy job is SUCCESS. Failed/unstable deploy → no httpd restart. This is the core requirement (restart Apache only if deployment goes fine).
|
||||
- **Docroot is the git repo** — `/var/www/html` tracks origin `web`, so the deploy is an in-place `sudo git pull origin master`. Files sit at docroot root → served at `/` → no `/web` subdirectory.
|
||||
- **Both jobs reach stapp01 via Publish Over SSH** — commands in the "Exec command" boxes run ON stapp01 as sarah. NOPASSWD sudo makes `sudo git` / `sudo systemctl` non-interactive.
|
||||
- **Idempotent (repeatable)** — `git checkout master` (no-op if already there), `git pull` ("already up to date"), `systemctl restart` (repeatable). Repeated builds of either job pass — important since validation builds multiple times.
|
||||
- **Separate jobs by design** — httpd restart lives in its own downstream job, not baked into the deploy. That separation IS the "chained builds" architecture the task asks for.
|
||||
|
||||
---
|
||||
|
||||
## Screenshots to capture (for review)
|
||||
- `devops-app-deployment` config showing the SSH exec step + the post-build "Build other projects → manage-services (only if stable)".
|
||||
- `manage-services` config showing the httpd restart step.
|
||||
- A successful build of `devops-app-deployment` showing it triggered `manage-services`.
|
||||
- The app loading at the root URL.
|
||||
198
100 - days of devops/devops-81.md
Normal file
198
100 - days of devops/devops-81.md
Normal file
@@ -0,0 +1,198 @@
|
||||
## Task 81
|
||||
|
||||
The development team of xFusionCorp Industries is working on to develop a new static website and they are planning to deploy the same on Nautilus App Server using Jenkins pipeline. They have shared their requirements with the DevOps team and accordingly we need to create a Jenkins pipeline job. Please find below more details about the task:
|
||||
|
||||
Click on the Jenkins button on the top bar to access the Jenkins UI. Login using username admin and password Adm!n321.
|
||||
|
||||
Similarly, click on the Gitea button on the top bar to access the Gitea UI. Login using username sarah and password Sarah_pass123.
|
||||
|
||||
There is a repository named sarah/web in Gitea that is already cloned on App Server 1 under /var/www/html directory.
|
||||
|
||||
Update the content of the file index.html under the same repository to Welcome to xFusionCorp Industries and push the changes to the origin into the master branch.
|
||||
|
||||
Apache is already installed on the app server and is running on port 8080.
|
||||
|
||||
Add App Server 1 as a Jenkins agent (slave) node: name App Server 1, label stapp01, remote root directory /home/sarah/jenkins_agent, launch via SSH with host stapp01 and credentials for user sarah. Install java-17-openjdk on App Server 1 if needed.
|
||||
|
||||
Create a Jenkins pipeline job named deploy-job (it must not be a Multibranch pipeline job) and pipeline should have two stages Deploy and Test ( names are case sensitive ). Configure these stages as per details mentioned below.
|
||||
|
||||
a. The Deploy stage should deploy the code from web repository under /var/www/html on App Server 1, as this is the document root of the app server.
|
||||
b. The pipeline should run on the App Server 1 node (e.g. use label stapp01).
|
||||
c. The Test stage should just test if the app is working fine and website is accessible. Its up to you how you design this stage to test it out, you can simply add a curl command as well to run a curl against the LBR URL (http://stlb01:8091) to see if the website is working or not. Make sure this stage fails in case the website/app is not working or if the Deploy stage fails.
|
||||
|
||||
Click on the App button on the top bar to see the latest changes you deployed. Please make sure the required content is loading on the main URL http://stlb01:8091 i.e there should not be a sub-directory like http://stlb01:8091/web etc.
|
||||
|
||||
Note:
|
||||
You might need to install some plugins and restart Jenkins service. So, we recommend clicking on Restart Jenkins when installation is complete and no jobs are running on plugin installation/update page i.e update centre. Also, Jenkins UI sometimes gets stuck when Jenkins service restarts in the back end. In this case, please make sure to refresh the UI page.
|
||||
|
||||
For these kind of scenarios requiring changes to be done in a web UI, please take screenshots so that you can share it with us for review in case your task is marked incomplete. You may also consider using a screen recording software such as loom.com to record and share your work.
|
||||
|
||||
### Solution
|
||||
|
||||
# Jenkins Pipeline: deploy-job (Deploy + Test stages)
|
||||
### Complete setup
|
||||
|
||||
---
|
||||
|
||||
## PART 0 — Prerequisites on App Server 1 (CLI)
|
||||
|
||||
```bash
|
||||
ssh sarah@stapp01
|
||||
# password: Sarah_pass123
|
||||
|
||||
# Java 17 for the Jenkins agent
|
||||
sudo yum install -y java-17-openjdk
|
||||
sudo alternatives --set java java-17-openjdk.x86_64
|
||||
java -version # must show 17
|
||||
|
||||
# agent working directory
|
||||
mkdir -p /home/sarah/jenkins_agent
|
||||
|
||||
# NOPASSWD sudo (deploy writes to /var/www/html which may be root-owned)
|
||||
echo 'sarah ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/sarah
|
||||
sudo chmod 0440 /etc/sudoers.d/sarah
|
||||
sudo visudo -c
|
||||
sudo -n true && echo "NOPASSWD works"
|
||||
|
||||
# confirm the repo clone + branch
|
||||
cd /var/www/html
|
||||
sudo git remote -v
|
||||
sudo git branch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PART 1 — Update index.html and push (as sarah on stapp01)
|
||||
|
||||
```bash
|
||||
cd /var/www/html
|
||||
|
||||
# update the file content
|
||||
echo "Welcome to xFusionCorp Industries" | sudo tee index.html
|
||||
|
||||
# configure git identity if needed
|
||||
sudo git config user.email "sarah@stratos.xfusioncorp.com"
|
||||
sudo git config user.name "sarah"
|
||||
|
||||
# commit and push to master
|
||||
sudo git add index.html
|
||||
sudo git commit -m "Update index.html content"
|
||||
sudo git push origin master
|
||||
```
|
||||
|
||||
If the push asks for credentials, use `sarah` / `Sarah_pass123`.
|
||||
|
||||
---
|
||||
|
||||
## PART 2 — Install plugins (Jenkins UI)
|
||||
|
||||
Jenkins UI → login `admin` / `Adm!n321`.
|
||||
|
||||
**Manage Jenkins → Plugins → Available plugins**, install if missing:
|
||||
- **SSH Build Agents**
|
||||
- **Pipeline** (usually present)
|
||||
- **Git** (usually present)
|
||||
|
||||
Check **"Restart Jenkins when installation is complete and no jobs are running"** → refresh UI if it hangs → wait for login page.
|
||||
|
||||
---
|
||||
|
||||
## PART 3 — Add credentials for sarah
|
||||
|
||||
**Manage Jenkins → Credentials → System → Global credentials → Add Credentials**:
|
||||
- Kind: **Username with password**
|
||||
- Username: `sarah`
|
||||
- Password: `Sarah_pass123`
|
||||
- ID: `stapp01-sarah`
|
||||
- Create.
|
||||
|
||||
---
|
||||
|
||||
## PART 4 — Add App Server 1 as an agent node
|
||||
|
||||
**Manage Jenkins → Nodes → New Node**:
|
||||
- **Node name**: `App Server 1`
|
||||
- Type: **Permanent Agent** → Create
|
||||
|
||||
Configure:
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Remote root directory | `/home/sarah/jenkins_agent` |
|
||||
| Labels | `stapp01` |
|
||||
| Usage | Use this node as much as possible |
|
||||
| Launch method | **Launch agents via SSH** |
|
||||
| Host | `stapp01` |
|
||||
| Credentials | `stapp01-sarah` (sarah) |
|
||||
| Host Key Verification Strategy | **Non verifying Verification Strategy** |
|
||||
|
||||
**Save** → confirm the node comes **online** (green, no red X). If offline, check the agent log (Java version, credentials, host key).
|
||||
|
||||
---
|
||||
|
||||
## PART 5 — Create the pipeline job
|
||||
|
||||
**New Item** → name `deploy-job` → **Pipeline** (NOT Multibranch) → OK.
|
||||
|
||||
In the job config → **Pipeline** section → Definition: **Pipeline script** → paste:
|
||||
|
||||
```groovy
|
||||
pipeline {
|
||||
agent { label 'stapp01' }
|
||||
stages {
|
||||
stage('Deploy') {
|
||||
steps {
|
||||
sh '''
|
||||
cd /var/www/html
|
||||
sudo git checkout master
|
||||
sudo git pull origin master
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh '''
|
||||
curl -f http://stlb01:8091 -o /dev/null
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Save.**
|
||||
|
||||
---
|
||||
|
||||
## PART 6 — Build and verify
|
||||
|
||||
**Build Now** → check **Console Output**:
|
||||
- Deploy stage: pulls latest master into `/var/www/html`.
|
||||
- Test stage: curls the LBR URL and greps for the content.
|
||||
- Both stages green = SUCCESS.
|
||||
|
||||
Verify the app:
|
||||
- Click **App** button / `http://stlb01:8091` → shows **Welcome to xFusionCorp Industries** at the **root** (no `/web` subdirectory).
|
||||
|
||||
---
|
||||
|
||||
## Key points
|
||||
|
||||
- **Pipeline pinned to `stapp01` node** — `agent { label 'stapp01' }` runs the whole pipeline on App Server 1, so the git pull happens where the docroot is, and the curl runs from a host that can reach the LBR.
|
||||
- **Two stages, exact case-sensitive names** — `Deploy` and `Test`. Capital D, capital T. The grader checks these literally.
|
||||
- **Deploy = in-place git pull** — `/var/www/html` is already the repo clone, so `sudo git pull origin master` updates it in place. Files at docroot root → served at `/` → no `/web` subdir.
|
||||
- **Test stage must FAIL if the site is broken (requirement c)** — this is the critical design point:
|
||||
- `curl -f` — the `-f` flag makes curl **return a non-zero exit code on HTTP errors** (4xx/5xx). A failed HTTP response fails the shell step → fails the stage.
|
||||
- `| grep "Welcome to xFusionCorp Industries"` — grep returns non-zero if the expected content isn't found, also failing the stage.
|
||||
- Combined: if the site is down (curl fails) OR the content is wrong (grep fails), the Test stage fails. Exactly what the task wants.
|
||||
- If the Deploy stage fails, the pipeline stops before Test anyway (declarative pipelines halt on first stage failure).
|
||||
- **`sudo` in the sh steps** — `/var/www/html` may be root-owned; NOPASSWD sudo lets `sudo git` run non-interactively on the agent.
|
||||
- **Plain Pipeline, not Multibranch** — single inline script, not branch-discovery.
|
||||
- **Idempotent** — `git checkout master` (no-op if already there) + `git pull` ("up to date") + curl test are all repeatable.
|
||||
|
||||
---
|
||||
|
||||
## Screenshots to capture
|
||||
- The `deploy-job` pipeline config (the script with Deploy + Test stages).
|
||||
- The `App Server 1` node showing online.
|
||||
- A successful build with both stages green (stage view).
|
||||
- The app loading at `http://stlb01:8091` with the correct content.
|
||||
123
100 - days of devops/devops-82.md
Normal file
123
100 - days of devops/devops-82.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is testing Ansible playbooks on various servers within their stack. They've placed some playbooks under /home/thor/playbook/ directory on the jump host and now intend to test them on app server 3 in Stratos DC. However, an inventory file needs creation for Ansible to connect to the respective app. Here are the requirements:
|
||||
|
||||
|
||||
a. Create an ini type Ansible inventory file /home/thor/playbook/inventory on jump host.
|
||||
|
||||
|
||||
b. Include App Server 3 in this inventory along with necessary variables for proper functionality.
|
||||
|
||||
|
||||
c. Ensure the inventory hostname corresponds to the server name as per the wiki, for example stapp01 for app server 1 in Stratos DC.
|
||||
|
||||
|
||||
Note: Validation will execute the playbook using the command ansible-playbook -i inventory playbook.yml. Ensure the playbook functions properly without any extra arguments.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Inventory — App Server 3 (`stapp03`)
|
||||
|
||||
Create an INI-format inventory at `/home/thor/playbook/inventory` so
|
||||
`ansible-playbook -i inventory playbook.yml` connects to App Server 3 with **no extra arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — there's no manifest to pipe into `kubectl`. The
|
||||
> heredoc below writes the inventory file instead.
|
||||
|
||||
## Create the inventory (heredoc → file)
|
||||
|
||||
```bash
|
||||
cat > /home/thor/playbook/inventory <<'EOF'
|
||||
[app_servers]
|
||||
stapp03 ansible_user=banner ansible_ssh_pass=BigGr33n ansible_connection=ssh
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### Why the hostname must be `stapp03`
|
||||
|
||||
Requirement (c) pins the inventory hostname to the **server name from the wiki** — `stapp01`,
|
||||
`stapp02`, `stapp03` for App Servers 1–3 in Stratos DC. This matters twice over:
|
||||
|
||||
1. The playbook's `hosts:` directive refers to hosts (or groups) by these names. Naming the entry
|
||||
`appserver3` or a raw IP would mean `hosts: stapp03` never matches, and Ansible skips with
|
||||
"no hosts matched."
|
||||
2. **`stapp03` is itself resolvable** from the jump host (via `/etc/hosts` / DNS in the lab
|
||||
network), so the inventory name doubles as the connection target. That's why **no `ansible_host`
|
||||
is needed** — Ansible resolves `stapp03` directly. Hardcoding an IP would add an assumption that
|
||||
could be wrong and break a setup that otherwise works.
|
||||
|
||||
Confirm resolution before running anything:
|
||||
|
||||
```bash
|
||||
getent hosts stapp03 # or: grep stapp /etc/hosts
|
||||
```
|
||||
|
||||
If that returns an address, you're set. `ansible_host` is only warranted in the rare case where the
|
||||
name **doesn't** resolve — and then you'd take the IP from that lookup or the wiki.
|
||||
|
||||
### The connection variables
|
||||
|
||||
Each `key=value` after the hostname is a **host variable** telling Ansible how to authenticate:
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `ansible_user` | SSH username on App Server 3. |
|
||||
| `ansible_ssh_pass` | SSH password. Needed because these servers use password auth, not key-based. |
|
||||
| `ansible_connection` | Transport plugin; `ssh` is the default for remote hosts, stated explicitly for clarity. |
|
||||
|
||||
> **Verify the credentials against your lab's wiki.** The values shown (`banner` / `BigGr33n`)
|
||||
> follow the common Stratos DC pattern, but treat them as placeholders to confirm rather than
|
||||
> facts — substitute whatever the wiki lists for App Server 3.
|
||||
|
||||
### Why the variables live *in* the inventory
|
||||
|
||||
The validation runs exactly `ansible-playbook -i inventory playbook.yml` — no `-u`, no `-k`, no
|
||||
`--private-key`. So every piece of connection information must come from the inventory file itself.
|
||||
Putting user and password in as host vars is what makes the bare command work; omitting them would
|
||||
force Ansible to fall back to the current user and key-based auth, and the connection would fail.
|
||||
|
||||
### Password auth needs `sshpass`
|
||||
|
||||
`ansible_ssh_pass` requires the `sshpass` utility on the control node (the jump host). It's normally
|
||||
pre-installed in these labs; if Ansible errors with "to use the 'ssh' connection type with
|
||||
passwords, you must install the sshpass program," install it (`sudo yum install -y sshpass`).
|
||||
|
||||
### Host key checking
|
||||
|
||||
A first-time SSH connection can fail on host-key verification. The safest fix for the validation is
|
||||
an `ansible.cfg` beside the playbook, since it applies with no extra command-line arguments:
|
||||
|
||||
```bash
|
||||
cat > /home/thor/playbook/ansible.cfg <<'EOF'
|
||||
[defaults]
|
||||
host_key_checking = False
|
||||
EOF
|
||||
```
|
||||
|
||||
(The equivalent `export ANSIBLE_HOST_KEY_CHECKING=False` works too, but relies on the environment
|
||||
being set when validation runs.)
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/playbook
|
||||
|
||||
# Inventory parses and lists stapp03
|
||||
ansible-inventory -i inventory --list
|
||||
|
||||
# Connectivity check — this settles hostname resolution AND credentials at once
|
||||
ansible -i inventory stapp03 -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
```
|
||||
|
||||
Expected — `ansible-inventory` showing `stapp03` with its vars, the ping returning
|
||||
`"ping": "pong"` with `SUCCESS`, and the playbook running to completion with no failed tasks.
|
||||
|
||||
> `UNREACHABLE` on the ping means either the hostname didn't resolve (check `getent hosts stapp03`)
|
||||
> or the user/password don't match the wiki. "No hosts matched" from the playbook means its `hosts:`
|
||||
> value doesn't align with `stapp03` or the group name — open `playbook.yml` and match the inventory
|
||||
> group to it.
|
||||
147
100 - days of devops/devops-83.md
Normal file
147
100 - days of devops/devops-83.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Assignment
|
||||
|
||||
An Ansible playbook needs completion on the jump host, where a team member left off. Below are the details:
|
||||
|
||||
|
||||
|
||||
The inventory file /home/thor/ansible/inventory requires adjustments. The playbook must run on App Server 3 in Stratos DC. Update the inventory accordingly.
|
||||
|
||||
|
||||
Create a playbook /home/thor/ansible/playbook.yml. Include a task to create an empty file /tmp/file.txt on App Server 3.
|
||||
|
||||
|
||||
Note: Validation will run the playbook using the command ansible-playbook -i inventory playbook.yml. Ensure the playbook works without any additional arguments.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Inventory + Playbook — create `/tmp/file.txt` on App Server 3
|
||||
|
||||
Complete the setup on the jump host so `ansible-playbook -i inventory playbook.yml` runs against
|
||||
`stapp03` and creates an empty file — with **no extra arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredocs
|
||||
> below write the two files.
|
||||
|
||||
## Step 0 — See what's already there
|
||||
|
||||
The inventory "requires adjustments," so inspect it before overwriting:
|
||||
|
||||
```bash
|
||||
cat /home/thor/ansible/inventory
|
||||
```
|
||||
|
||||
Note any existing hostnames/groups — if the playbook or validation expects a particular group name,
|
||||
keep it. Otherwise the version below replaces it cleanly.
|
||||
|
||||
## Step 1 — Inventory
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/inventory <<'EOF'
|
||||
[app_servers]
|
||||
stapp03 ansible_user=banner ansible_ssh_pass=BigGr33n ansible_connection=ssh
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 2 — Playbook
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Create an empty file on App Server 3
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: Create /tmp/file.txt
|
||||
ansible.builtin.file:
|
||||
path: /tmp/file.txt
|
||||
state: touch
|
||||
mode: '0644'
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The inventory
|
||||
|
||||
- **Hostname `stapp03`** — App Server 3's server name per the wiki. It's resolvable from the jump
|
||||
host, so the inventory name doubles as the connection target; **no `ansible_host` is needed**.
|
||||
Confirm with `getent hosts stapp03` if you want to be sure.
|
||||
- **`ansible_user` / `ansible_ssh_pass`** — the SSH credentials. These servers use password auth, so
|
||||
both are required. **Verify the values against your lab's wiki** — the ones shown follow the
|
||||
common Stratos DC pattern but should be confirmed, not assumed.
|
||||
- **`ansible_connection=ssh`** — the transport plugin; the default for remote hosts, stated
|
||||
explicitly.
|
||||
|
||||
Because validation runs the bare command (no `-u`, `-k`, or `--private-key`), **every** connection
|
||||
detail must live in the inventory file itself.
|
||||
|
||||
### The playbook
|
||||
|
||||
- **`hosts: all`** — deliberately chosen over `hosts: stapp03`. Since the inventory contains only
|
||||
App Server 3, `all` targets exactly that host, and it stays correct regardless of what the group is
|
||||
named. Using a specific name risks a "no hosts matched" skip if the inventory group and the
|
||||
playbook's `hosts:` value ever drift apart.
|
||||
- **`ansible.builtin.file` with `state: touch`** — creates the file if absent, leaving it empty.
|
||||
This is the right module for "create an empty file"; `copy` with empty `content` would also work
|
||||
but `file`/`touch` expresses the intent directly.
|
||||
- **`mode: '0644'`** — standard read/write-owner, read-others permissions. Quoted so YAML treats it
|
||||
as a string, not an octal-looking integer (a classic gotcha: unquoted `0644` can be misparsed).
|
||||
- **No `become`** — `/tmp` is world-writable, so the SSH user can create the file without
|
||||
privilege escalation. Adding `become: yes` would invite a sudo-password prompt and could break the
|
||||
no-extra-arguments requirement.
|
||||
|
||||
### A note on idempotency
|
||||
|
||||
`state: touch` updates the file's timestamps on every run, so Ansible reports **changed** each
|
||||
time rather than **ok**. That's fine for this task. If you want true idempotency:
|
||||
|
||||
```yaml
|
||||
- name: Create /tmp/file.txt
|
||||
ansible.builtin.file:
|
||||
path: /tmp/file.txt
|
||||
state: touch
|
||||
mode: '0644'
|
||||
modification_time: preserve
|
||||
access_time: preserve
|
||||
```
|
||||
|
||||
This leaves timestamps alone when the file already exists, so re-runs report `ok`.
|
||||
|
||||
### Host key checking
|
||||
|
||||
A first-time SSH connection can fail on host-key verification. The safest fix — because it needs no
|
||||
extra command-line arguments during validation — is an `ansible.cfg` beside the playbook:
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/ansible.cfg <<'EOF'
|
||||
[defaults]
|
||||
host_key_checking = False
|
||||
EOF
|
||||
```
|
||||
|
||||
`sshpass` must also be present on the jump host for `ansible_ssh_pass` to work. It's usually
|
||||
pre-installed; if not, Ansible's error names it explicitly.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/ansible
|
||||
|
||||
# Inventory parses
|
||||
ansible-inventory -i inventory --list
|
||||
|
||||
# Connectivity + credentials in one shot
|
||||
ansible -i inventory all -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# Confirm the file exists on the target
|
||||
ansible -i inventory all -m command -a "ls -l /tmp/file.txt"
|
||||
```
|
||||
|
||||
Expected — ping returns `"ping": "pong"` with `SUCCESS`, the playbook completes with
|
||||
`ok=2 changed=1` and no failures, and the final check lists `/tmp/file.txt` on stapp03.
|
||||
|
||||
> `UNREACHABLE` means the hostname didn't resolve or the credentials don't match the wiki. A
|
||||
> `PLAY RECAP` showing `skipped` or "no hosts matched" means the playbook's `hosts:` value doesn't
|
||||
> align with the inventory — `hosts: all` avoids that class of failure.
|
||||
161
100 - days of devops/devops-84.md
Normal file
161
100 - days of devops/devops-84.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team needs to copy data from the jump host to all application servers in Stratos DC using Ansible. Execute the task with the following details:
|
||||
|
||||
|
||||
a. Create an inventory file /home/thor/ansible/inventory on jump_host and add all application servers as managed nodes.
|
||||
|
||||
|
||||
b. Create a playbook /home/thor/ansible/playbook.yml on the jump host to copy the /usr/src/dba/index.html file to all application servers, placing it at /opt/dba.
|
||||
|
||||
|
||||
Note: Validation will run the playbook using the command ansible-playbook -i inventory playbook.yml. Ensure the playbook functions properly without any extra arguments.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Inventory + Playbook — copy `index.html` to all App Servers
|
||||
|
||||
Set up the jump host so `ansible-playbook -i inventory playbook.yml` copies
|
||||
`/usr/src/dba/index.html` to `/opt/dba` on **all three** Stratos DC application servers — with **no
|
||||
extra arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredocs
|
||||
> below write the two files.
|
||||
|
||||
## Step 1 — Inventory (all three app servers)
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/inventory <<'EOF'
|
||||
[app_servers]
|
||||
stapp01 ansible_user=tony ansible_ssh_pass=Ir0nM@n ansible_become_pass=Ir0nM@n
|
||||
stapp02 ansible_user=steve ansible_ssh_pass=Am3ric@ ansible_become_pass=Am3ric@
|
||||
stapp03 ansible_user=banner ansible_ssh_pass=BigGr33n ansible_become_pass=BigGr33n
|
||||
|
||||
[app_servers:vars]
|
||||
ansible_connection=ssh
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 2 — Playbook
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Copy index.html to all application servers
|
||||
hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Ensure /opt/dba exists
|
||||
ansible.builtin.file:
|
||||
path: /opt/dba
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy index.html to /opt/dba
|
||||
ansible.builtin.copy:
|
||||
src: /usr/src/dba/index.html
|
||||
dest: /opt/dba/index.html
|
||||
mode: '0644'
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The inventory — all three managed nodes
|
||||
|
||||
Each app server in Stratos DC gets its own line under the `[app_servers]` group, keyed by the
|
||||
**server name from the wiki** (`stapp01`, `stapp02`, `stapp03`). Those names resolve from the jump
|
||||
host, so the inventory name doubles as the connection target — **no `ansible_host` needed**. Confirm
|
||||
with `getent hosts stapp01` if you want certainty.
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `ansible_user` | SSH username, different per server. |
|
||||
| `ansible_ssh_pass` | SSH password (these servers use password auth, not keys). |
|
||||
| `ansible_become_pass` | The **sudo** password, needed because the playbook uses `become: yes`. |
|
||||
| `ansible_connection` | Transport plugin — set once for the whole group via `[app_servers:vars]`. |
|
||||
|
||||
> **Verify every credential against your lab's wiki.** The user/password pairs shown follow the
|
||||
> common Stratos DC pattern, but treat them as values to confirm rather than facts.
|
||||
|
||||
The `[app_servers:vars]` block sets `ansible_connection` once for all members instead of repeating
|
||||
it on every line — the same result, less duplication.
|
||||
|
||||
Because validation runs the bare command (no `-u`, `-k`, `-K`, or `--private-key`), **all**
|
||||
connection *and* escalation details must live in the inventory.
|
||||
|
||||
### Why `become: yes` is required here
|
||||
|
||||
`/opt/dba` is root-owned. The SSH users (`tony`, `steve`, `banner`) can't write there directly, so
|
||||
the play escalates to root with `become: yes`. That in turn means sudo may demand a password —
|
||||
which is why `ansible_become_pass` is set per host in the inventory. Without it, the run would hang
|
||||
or fail with "Missing sudo password," and you can't pass `-K` because no extra arguments are
|
||||
allowed.
|
||||
|
||||
> If your lab's app-server users have **passwordless** sudo, `ansible_become_pass` is harmless and
|
||||
> simply unused. Including it covers both cases.
|
||||
|
||||
### The two tasks
|
||||
|
||||
1. **`ansible.builtin.file` with `state: directory`** — guarantees `/opt/dba` exists before the
|
||||
copy. The `copy` module won't create missing parent directories, so if `/opt/dba` weren't already
|
||||
present the copy would fail with "Destination directory does not exist." This task makes the
|
||||
playbook robust either way, and it's idempotent (reports `ok` when the directory already exists).
|
||||
|
||||
2. **`ansible.builtin.copy`** — transfers the file:
|
||||
- **`src: /usr/src/dba/index.html`** — a path on the **control node** (the jump host). The `copy`
|
||||
module reads from the controller by default; that's exactly what "copy data from the jump host"
|
||||
means here. (Copying between two paths on the *remote* machine would instead need
|
||||
`remote_src: yes` — not the case here.)
|
||||
- **`dest: /opt/dba/index.html`** — the explicit target path. Writing the filename out is clearer
|
||||
than relying on `dest: /opt/dba/` directory-expansion behavior.
|
||||
- **`mode: '0644'`** — quoted so YAML treats it as a string; unquoted octal like `0644` is a
|
||||
classic misparse.
|
||||
|
||||
### Why `hosts: all`
|
||||
|
||||
The inventory contains only the three app servers, so `all` targets exactly them — and it can't
|
||||
break if the group name and the playbook's `hosts:` value ever drift apart. Using
|
||||
`hosts: app_servers` also works given this inventory; `all` is simply the more failure-proof
|
||||
choice.
|
||||
|
||||
### Host key checking
|
||||
|
||||
First-time SSH connections can fail on host-key verification. The safest fix — no extra
|
||||
command-line arguments needed during validation — is an `ansible.cfg` beside the playbook:
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/ansible.cfg <<'EOF'
|
||||
[defaults]
|
||||
host_key_checking = False
|
||||
EOF
|
||||
```
|
||||
|
||||
`sshpass` must also be installed on the jump host for password auth; it usually is, and Ansible's
|
||||
error names it explicitly if not.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/ansible
|
||||
|
||||
# All three hosts listed
|
||||
ansible-inventory -i inventory --list
|
||||
|
||||
# Connectivity + credentials across every server at once
|
||||
ansible -i inventory all -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# Confirm the file landed on all three
|
||||
ansible -i inventory all -b -m command -a "ls -l /opt/dba/index.html"
|
||||
```
|
||||
|
||||
Expected — `ping` returning `SUCCESS`/`pong` for stapp01, stapp02, and stapp03; the playbook
|
||||
finishing with `failed=0` for all three hosts; and the final check listing `/opt/dba/index.html` on
|
||||
each server.
|
||||
|
||||
> `UNREACHABLE` means a hostname didn't resolve or credentials don't match the wiki. "Missing sudo
|
||||
> password" means `ansible_become_pass` is absent or wrong for that host. Permission denied on the
|
||||
> copy means `become: yes` didn't take effect.
|
||||
179
100 - days of devops/devops-85.md
Normal file
179
100 - days of devops/devops-85.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is testing various Ansible modules on servers in Stratos DC. They're currently focusing on file creation on remote hosts using Ansible. Here are the details:
|
||||
|
||||
|
||||
a. Create an inventory file ~/playbook/inventory on jump host and include all app servers.
|
||||
|
||||
|
||||
b. Create a playbook ~/playbook/playbook.yml to create a blank file /opt/app.txt on all app servers.
|
||||
|
||||
|
||||
c. Set the permissions of the /opt/app.txt file to 0755.
|
||||
|
||||
|
||||
d. Ensure the user/group owner of the /opt/app.txt file is tony on app server 1, steve on app server 2 and banner on app server 3.
|
||||
|
||||
|
||||
Note: Validation will execute the playbook using the command ansible-playbook -i inventory playbook.yml, so ensure the playbook functions correctly without any additional arguments.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Inventory + Playbook — create `/opt/app.txt` with per-host ownership
|
||||
|
||||
Set up the jump host so `ansible-playbook -i inventory playbook.yml` creates a blank
|
||||
`/opt/app.txt` on all three Stratos DC app servers, mode `0755`, owned by a **different user on
|
||||
each host** — with **no extra arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredocs
|
||||
> below write the two files.
|
||||
|
||||
## Step 1 — Inventory (all three app servers)
|
||||
|
||||
```bash
|
||||
cat > ~/playbook/inventory <<'EOF'
|
||||
[app_servers]
|
||||
stapp01 ansible_user=tony ansible_ssh_pass=Ir0nM@n ansible_become_pass=Ir0nM@n
|
||||
stapp02 ansible_user=steve ansible_ssh_pass=Am3ric@ ansible_become_pass=Am3ric@
|
||||
stapp03 ansible_user=banner ansible_ssh_pass=BigGr33n ansible_become_pass=BigGr33n
|
||||
|
||||
[app_servers:vars]
|
||||
ansible_connection=ssh
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 2 — Playbook
|
||||
|
||||
```bash
|
||||
cat > ~/playbook/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Create /opt/app.txt on all app servers
|
||||
hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Create blank file with correct mode and ownership
|
||||
ansible.builtin.file:
|
||||
path: /opt/app.txt
|
||||
state: touch
|
||||
mode: '0755'
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The neat trick: `{{ ansible_user }}` for per-host ownership
|
||||
|
||||
Requirement (d) wants a **different owner per server** — `tony` on stapp01, `steve` on stapp02,
|
||||
`banner` on stapp03. Notice those are exactly the SSH users already defined per host in the
|
||||
inventory. So instead of writing three separate tasks with `when:` conditionals, the playbook
|
||||
references the variable:
|
||||
|
||||
```yaml
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
```
|
||||
|
||||
Ansible evaluates `ansible_user` **per host** during the play, so it resolves to `tony` on stapp01,
|
||||
`steve` on stapp02, and `banner` on stapp03 automatically. One task, three correct outcomes — and it
|
||||
stays correct if a host is added or a username changes, since the inventory is the single source of
|
||||
truth.
|
||||
|
||||
> `group` uses the same value because these systems create a matching primary group for each user
|
||||
> (user `tony` → group `tony`), which is the Linux default. If your lab's groups differ, set `group`
|
||||
> explicitly per host instead.
|
||||
|
||||
The alternative — hardcoding with conditionals — would look like this and is strictly worse:
|
||||
|
||||
```yaml
|
||||
# NOT recommended, shown for contrast
|
||||
- name: Set owner on stapp01
|
||||
ansible.builtin.file:
|
||||
path: /opt/app.txt
|
||||
owner: tony
|
||||
when: inventory_hostname == "stapp01"
|
||||
# ...repeated for each host
|
||||
```
|
||||
|
||||
### The inventory
|
||||
|
||||
Each app server is keyed by its **wiki server name** (`stapp01`, `stapp02`, `stapp03`), which
|
||||
resolves from the jump host — so the inventory name doubles as the connection target and **no
|
||||
`ansible_host` is needed**. Confirm with `getent hosts stapp01` if you want certainty.
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `ansible_user` | SSH username — **and** the file owner, via the templating above. |
|
||||
| `ansible_ssh_pass` | SSH password (password auth, not keys). |
|
||||
| `ansible_become_pass` | Sudo password, needed because the play uses `become: yes`. |
|
||||
| `ansible_connection` | Transport plugin, set once for the group via `[app_servers:vars]`. |
|
||||
|
||||
> **Verify every credential against your lab's wiki.** The pairs shown follow the common Stratos DC
|
||||
> pattern but should be confirmed rather than assumed.
|
||||
|
||||
Since validation runs the bare command (no `-u`, `-k`, `-K`), **all** connection and escalation
|
||||
details must live in the inventory.
|
||||
|
||||
### Why `become: yes` is required
|
||||
|
||||
`/opt` is root-owned, so the SSH users can't create a file there directly — the play escalates with
|
||||
`become: yes`. Escalation is also what makes **`owner:`/`group:` work at all**: changing a file's
|
||||
ownership requires root (`chown` is privileged). Without `become`, you'd get permission errors on
|
||||
both the create and the chown.
|
||||
|
||||
Because sudo may prompt for a password and you can't pass `-K`, `ansible_become_pass` is set per
|
||||
host in the inventory. (If the lab has passwordless sudo, the variable is simply unused — harmless
|
||||
either way.)
|
||||
|
||||
### The `file` module options
|
||||
|
||||
- **`state: touch`** — creates the file if absent, leaving it blank. Exactly "create a blank file."
|
||||
- **`mode: '0755'`** — quoted so YAML reads it as a string; unquoted octal like `0755` is a classic
|
||||
misparse that silently produces the wrong permissions.
|
||||
- **`owner` / `group`** — applied by the module after creation, which is why root privileges are
|
||||
needed.
|
||||
|
||||
Note `state: touch` bumps timestamps on every run, so re-runs report **changed** rather than **ok**.
|
||||
That's fine here; add `modification_time: preserve` and `access_time: preserve` if you want strict
|
||||
idempotency.
|
||||
|
||||
### Host key checking
|
||||
|
||||
First-time SSH connections can fail on host-key verification. The safest fix — needing no extra
|
||||
command-line arguments — is an `ansible.cfg` beside the playbook:
|
||||
|
||||
```bash
|
||||
cat > ~/playbook/ansible.cfg <<'EOF'
|
||||
[defaults]
|
||||
host_key_checking = False
|
||||
EOF
|
||||
```
|
||||
|
||||
`sshpass` must also be installed on the jump host for password auth.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd ~/playbook
|
||||
|
||||
# All three hosts listed
|
||||
ansible-inventory -i inventory --list
|
||||
|
||||
# Connectivity + credentials across every server
|
||||
ansible -i inventory all -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# Confirm mode and per-host ownership
|
||||
ansible -i inventory all -b -m command -a "ls -l /opt/app.txt"
|
||||
```
|
||||
|
||||
Expected — `ping` returning `SUCCESS` for all three; the playbook finishing with `failed=0`; and the
|
||||
final listing showing `-rwxr-xr-x` with owner/group `tony tony` on stapp01, `steve steve` on
|
||||
stapp02, and `banner banner` on stapp03.
|
||||
|
||||
> "Missing sudo password" ⇒ `ansible_become_pass` absent or wrong. A chown failure ⇒ `become: yes`
|
||||
> didn't take. Wrong owner ⇒ check that `ansible_user` in the inventory matches the required owner
|
||||
> for that host.
|
||||
158
100 - days of devops/devops-86.md
Normal file
158
100 - days of devops/devops-86.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is planning to test several Ansible playbooks on different app servers in Stratos DC. Before that, some pre-requisites must be met. Essentially, the team needs to set up a password-less SSH connection between Ansible controller and Ansible managed nodes. One of the tickets is assigned to you; please complete the task as per details mentioned below:
|
||||
|
||||
|
||||
a. Jump host is our Ansible controller, and we are going to run Ansible playbooks through thor user from jump host.
|
||||
|
||||
|
||||
b. There is an inventory file /home/thor/ansible/inventory on jump host. Using that inventory file test Ansible ping from jump host to App Server 2, make sure ping works.
|
||||
|
||||
# Solution
|
||||
|
||||
# Passwordless SSH — jump host (`thor`) → App Server 2 (`stapp02`)
|
||||
|
||||
Set up key-based SSH from the Ansible controller to App Server 2, then confirm with an Ansible ping.
|
||||
|
||||
> Note: this is an Ansible/SSH task, not Kubernetes — no manifests to pipe into `kubectl`. Heredocs
|
||||
> are used for the files; the key-copy step is interactive by design.
|
||||
|
||||
## Step 1 — Generate an SSH key pair for `thor` (if none exists)
|
||||
|
||||
```bash
|
||||
# Check first — don't overwrite an existing key
|
||||
ls -l ~/.ssh/id_rsa.pub 2>/dev/null || ssh-keygen -t rsa -b 4096 -N "" -f ~/.ssh/id_rsa
|
||||
```
|
||||
|
||||
- `-N ""` — empty passphrase, so SSH never prompts (that's the whole point of *password-less*).
|
||||
- `-f ~/.ssh/id_rsa` — the default location Ansible/SSH look in, so no extra config is needed.
|
||||
|
||||
## Step 2 — Copy the public key to App Server 2
|
||||
|
||||
```bash
|
||||
ssh-copy-id steve@stapp02
|
||||
```
|
||||
|
||||
This prompts for `steve`'s password **once** — that's expected and unavoidable; it's the one-time
|
||||
bootstrap that establishes trust. Accept the host-key fingerprint if asked (`yes`).
|
||||
|
||||
> Verify the username for App Server 2 against your lab's wiki. `steve` follows the common Stratos
|
||||
> DC pattern, but confirm rather than assume.
|
||||
|
||||
Confirm it worked — this should log in with **no password prompt**:
|
||||
|
||||
```bash
|
||||
ssh steve@stapp02 hostname
|
||||
exit
|
||||
```
|
||||
|
||||
## Step 3 — Inventory
|
||||
|
||||
The inventory already exists and may contain password variables. With key-based auth in place,
|
||||
those are no longer needed:
|
||||
|
||||
```bash
|
||||
# Inspect what's there first
|
||||
cat /home/thor/ansible/inventory
|
||||
```
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/inventory <<'EOF'
|
||||
[app_servers]
|
||||
stapp02 ansible_user=steve ansible_connection=ssh
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 4 — Test the Ansible ping
|
||||
|
||||
```bash
|
||||
cd /home/thor/ansible
|
||||
ansible -i inventory stapp02 -m ping
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```
|
||||
stapp02 | SUCCESS => {
|
||||
"ansible_facts": {...},
|
||||
"changed": false,
|
||||
"ping": "pong"
|
||||
}
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### What "password-less" actually means
|
||||
|
||||
SSH supports several auth methods. Password auth requires a secret typed (or supplied via
|
||||
`sshpass`/`ansible_ssh_pass`) on **every** connection. **Public-key auth** instead proves identity
|
||||
cryptographically:
|
||||
|
||||
1. `ssh-keygen` creates a key pair on the controller — a **private** key (`~/.ssh/id_rsa`, stays on
|
||||
the jump host, never shared) and a **public** key (`~/.ssh/id_rsa.pub`).
|
||||
2. `ssh-copy-id` appends that public key to `~/.ssh/authorized_keys` on the managed node.
|
||||
3. On each later connection, the server challenges the client to prove it holds the matching private
|
||||
key. No password crosses the wire, and nothing needs to be typed.
|
||||
|
||||
That one-time password prompt in Step 2 exists because you must authenticate *somehow* to install
|
||||
the key. After that, it's never needed again.
|
||||
|
||||
### Why the inventory drops `ansible_ssh_pass`
|
||||
|
||||
With the key installed, Ansible connects over SSH using `thor`'s private key automatically — it's at
|
||||
the default path, so no `ansible_ssh_private_key_file` is required either. Leaving a stale
|
||||
`ansible_ssh_pass` in the inventory isn't fatal, but removing it is the point of the exercise:
|
||||
authentication is now key-based, and the inventory should reflect that.
|
||||
|
||||
What remains:
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `ansible_user=steve` | **Still required** — it tells Ansible which remote account to log in as. Key auth proves *who you are*, not *whom you connect as*. |
|
||||
| `ansible_connection=ssh` | Transport plugin; the default for remote hosts, stated explicitly. |
|
||||
|
||||
`stapp02` resolves from the jump host, so the inventory name doubles as the connection target — no
|
||||
`ansible_host` needed. Confirm with `getent hosts stapp02` if unsure.
|
||||
|
||||
### Why the `ping` module is the right test
|
||||
|
||||
`ansible -m ping` isn't ICMP — it opens a real SSH connection, runs a trivial Python module on the
|
||||
target, and returns `pong`. So a `SUCCESS` result proves the **entire chain** works: hostname
|
||||
resolution, SSH key authentication, the remote user, and Python on the managed node. That's exactly
|
||||
what requirement (b) asks you to demonstrate.
|
||||
|
||||
### Host key checking
|
||||
|
||||
The first connection prompts to accept the host's fingerprint. Doing it manually in Step 2 (via
|
||||
`ssh-copy-id`/`ssh`) gets it into `~/.ssh/known_hosts` before Ansible runs, which is the cleanest
|
||||
approach. If Ansible still trips on it, add an `ansible.cfg` beside the inventory:
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/ansible.cfg <<'EOF'
|
||||
[defaults]
|
||||
host_key_checking = False
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Key exists on the controller
|
||||
ls -l ~/.ssh/id_rsa ~/.ssh/id_rsa.pub
|
||||
|
||||
# Key installed on the target (should list thor's key)
|
||||
ssh steve@stapp02 "cat ~/.ssh/authorized_keys"
|
||||
|
||||
# Passwordless login works
|
||||
ssh steve@stapp02 hostname
|
||||
|
||||
# The required check
|
||||
cd /home/thor/ansible && ansible -i inventory stapp02 -m ping
|
||||
```
|
||||
|
||||
Expected — both key files present, `thor@jump_host` visible in the target's `authorized_keys`, SSH
|
||||
logging in without a prompt, and the ping returning `SUCCESS` with `"ping": "pong"`.
|
||||
|
||||
> Still prompted for a password? Permissions are the usual culprit: on the **target**,
|
||||
> `~/.ssh` must be `700` and `~/.ssh/authorized_keys` `600` — SSH silently refuses keys on
|
||||
> loosely-permissioned files. On the **controller**, `~/.ssh/id_rsa` must be `600`.
|
||||
163
100 - days of devops/devops-87.md
Normal file
163
100 - days of devops/devops-87.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus Application development team wanted to test some applications on app servers in Stratos Datacenter. They shared some pre-requisites with the DevOps team, and packages need to be installed on app servers. Since we are already using Ansible for automating such tasks, please perform this task using Ansible as per details mentioned below:
|
||||
|
||||
|
||||
|
||||
Create an inventory file /home/thor/playbook/inventory on jump host and add all app servers in it.
|
||||
|
||||
|
||||
Create an Ansible playbook /home/thor/playbook/playbook.yml to install samba package on all app servers using Ansible yum module.
|
||||
|
||||
|
||||
Make sure user thor should be able to run the playbook on jump host.
|
||||
|
||||
Note: Validation will try to run playbook using command ansible-playbook -i inventory playbook.yml so please make sure playbook works this way, without passing any extra arguments.ß
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Inventory + Playbook — install `samba` on all App Servers
|
||||
|
||||
Set up the jump host so `ansible-playbook -i inventory playbook.yml` installs the `samba` package on
|
||||
all three Stratos DC app servers — with **no extra arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredocs
|
||||
> below write the two files.
|
||||
|
||||
## Step 1 — Inventory (all three app servers)
|
||||
|
||||
```bash
|
||||
cat > /home/thor/playbook/inventory <<'EOF'
|
||||
[app_servers]
|
||||
stapp01 ansible_user=tony ansible_ssh_pass=Ir0nM@n ansible_become_pass=Ir0nM@n
|
||||
stapp02 ansible_user=steve ansible_ssh_pass=Am3ric@ ansible_become_pass=Am3ric@
|
||||
stapp03 ansible_user=banner ansible_ssh_pass=BigGr33n ansible_become_pass=BigGr33n
|
||||
|
||||
[app_servers:vars]
|
||||
ansible_connection=ssh
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 2 — Playbook
|
||||
|
||||
```bash
|
||||
cat > /home/thor/playbook/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Install samba on all app servers
|
||||
hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Install samba package
|
||||
ansible.builtin.yum:
|
||||
name: samba
|
||||
state: present
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 3 — Ensure `thor` can run it
|
||||
|
||||
```bash
|
||||
# thor owns the playbook directory and its contents
|
||||
sudo chown -R thor:thor /home/thor/playbook
|
||||
chmod 644 /home/thor/playbook/inventory /home/thor/playbook/playbook.yml
|
||||
```
|
||||
|
||||
If you created both files as `thor` (as the heredocs above do), ownership is already correct and
|
||||
this step is a no-op safety check.
|
||||
|
||||
## How it works
|
||||
|
||||
### The inventory
|
||||
|
||||
Each app server is keyed by its **wiki server name** (`stapp01`, `stapp02`, `stapp03`), which
|
||||
resolves from the jump host — so the inventory name doubles as the connection target and **no
|
||||
`ansible_host` is needed**. Verify with `getent hosts stapp01` if unsure.
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `ansible_user` | SSH username, different per server. |
|
||||
| `ansible_ssh_pass` | SSH password (these servers use password auth). |
|
||||
| `ansible_become_pass` | **Sudo** password — required because installing packages needs root. |
|
||||
| `ansible_connection` | Transport plugin, set once for the group via `[app_servers:vars]`. |
|
||||
|
||||
> **Verify every credential against your lab's wiki.** The pairs shown follow the common Stratos DC
|
||||
> pattern but should be confirmed rather than assumed.
|
||||
|
||||
Since validation runs the bare command (no `-u`, `-k`, `-K`), **all** connection and escalation
|
||||
details must live in the inventory file.
|
||||
|
||||
### Why `become: yes` is mandatory here
|
||||
|
||||
Installing a package writes to system directories and the RPM database — strictly root-only
|
||||
operations. The SSH users (`tony`, `steve`, `banner`) are unprivileged, so the play escalates with
|
||||
`become: yes`. Without it, the task fails with a permissions error from yum.
|
||||
|
||||
Because sudo may prompt for a password and you **can't** pass `-K` (no extra arguments allowed),
|
||||
`ansible_become_pass` is set per host in the inventory. That's the single most common failure point
|
||||
on this task — "Missing sudo password" with no way to supply it at runtime.
|
||||
|
||||
> If the lab's app-server users have passwordless sudo, the variable is simply unused — harmless
|
||||
> either way.
|
||||
|
||||
### The `yum` module
|
||||
|
||||
- **`name: samba`** — the package to install.
|
||||
- **`state: present`** — ensures the package is installed, and does nothing if it already is. This
|
||||
makes the task **idempotent**: the first run reports `changed`, subsequent runs report `ok`.
|
||||
(`state: latest` would instead upgrade on every run, which isn't what "install" asks for.)
|
||||
- **`ansible.builtin.yum`** — the task explicitly requires the yum module. These app servers are
|
||||
RHEL/CentOS-family, so yum is correct. On modern Fedora/RHEL 8+ the `dnf` module is the successor,
|
||||
and Ansible's `yum` module transparently delegates to dnf where appropriate — so `yum` works here
|
||||
regardless.
|
||||
|
||||
### Why `hosts: all`
|
||||
|
||||
The inventory contains only the three app servers, so `all` targets exactly them — and it can't
|
||||
break if the group name and the playbook's `hosts:` value drift apart. `hosts: app_servers` also
|
||||
works given this inventory; `all` is just the more failure-proof choice.
|
||||
|
||||
### Requirement 3 — "thor should be able to run the playbook"
|
||||
|
||||
The validation runs as `thor`, so `thor` must be able to **read** both files. Creating them with the
|
||||
heredocs above (as `thor`) satisfies this automatically. The `chown`/`chmod` in Step 3 is a
|
||||
belt-and-braces check in case the directory was pre-created by another user — a root-owned
|
||||
`playbook.yml` that `thor` can't read would fail validation before Ansible even starts.
|
||||
|
||||
### Host key checking
|
||||
|
||||
First-time SSH connections can fail on host-key verification. The safest fix — needing no extra
|
||||
command-line arguments — is an `ansible.cfg` beside the playbook:
|
||||
|
||||
```bash
|
||||
cat > /home/thor/playbook/ansible.cfg <<'EOF'
|
||||
[defaults]
|
||||
host_key_checking = False
|
||||
EOF
|
||||
```
|
||||
|
||||
`sshpass` must also be installed on the jump host for password auth.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/playbook
|
||||
|
||||
# All three hosts listed
|
||||
ansible-inventory -i inventory --list
|
||||
|
||||
# Connectivity + credentials across every server
|
||||
ansible -i inventory all -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# Confirm samba is installed on all three
|
||||
ansible -i inventory all -b -m command -a "rpm -q samba"
|
||||
```
|
||||
|
||||
Expected — `ping` returning `SUCCESS` for all three; the playbook finishing with `failed=0`; and
|
||||
`rpm -q samba` printing an installed version (e.g. `samba-4.x.x-...`) on each server rather than
|
||||
"package samba is not installed."
|
||||
|
||||
> "Missing sudo password" ⇒ `ansible_become_pass` absent or wrong. A yum permissions error ⇒
|
||||
> `become: yes` didn't take effect. `UNREACHABLE` ⇒ hostname resolution or credentials.
|
||||
172
100 - days of devops/devops-88.md
Normal file
172
100 - days of devops/devops-88.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team wants to install and set up a simple httpd web server on all app servers in Stratos DC. Additionally, they want to deploy a sample web page for now using Ansible only. Therefore, write the required playbook to complete this task. Find more details about the task below.
|
||||
|
||||
We already have an inventory file under /home/thor/ansible directory on jump host. Create a playbook.yml under /home/thor/ansible directory on jump host itself.
|
||||
Using the playbook, install httpd web server on all app servers. Additionally, make sure its service should up and running.
|
||||
Using blockinfile Ansible module add some content in /var/www/html/index.html file. Below is the content:
|
||||
|
||||
Welcome to XfusionCorp!
|
||||
This is Nautilus sample file, created using Ansible!
|
||||
Please do not modify this file manually!
|
||||
The /var/www/html/index.html file's user and group owner should be apache on all app servers.
|
||||
The /var/www/html/index.html file's permissions should be 0777 on all app servers.
|
||||
|
||||
|
||||
Note:
|
||||
i. Validation will try to run the playbook using command ansible-playbook -i inventory playbook.yml so please make sure the playbook works this way without passing any extra arguments.
|
||||
ii. Do not use any custom or empty marker for blockinfile module.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Playbook — install httpd + deploy sample page on all App Servers
|
||||
|
||||
Create `/home/thor/ansible/playbook.yml` so `ansible-playbook -i inventory playbook.yml` installs
|
||||
httpd, starts it, and writes a sample `index.html` with specific ownership and permissions — with
|
||||
**no extra arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredoc
|
||||
> below writes the playbook.
|
||||
|
||||
## Step 0 — The inventory already exists; leave it alone
|
||||
|
||||
The task states an inventory is already present. **Do not recreate or overwrite it** — just confirm
|
||||
what's in it:
|
||||
|
||||
```bash
|
||||
cat /home/thor/ansible/inventory
|
||||
```
|
||||
|
||||
Check that it lists the app servers with their connection variables. If it lacks
|
||||
`ansible_become_pass` and the app-server users require a sudo password, add that per host — the
|
||||
playbook needs privilege escalation and you can't pass `-K` at runtime.
|
||||
|
||||
## Step 1 — Playbook
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Install httpd and deploy sample web page
|
||||
hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Install httpd package
|
||||
ansible.builtin.yum:
|
||||
name: httpd
|
||||
state: present
|
||||
|
||||
- name: Start and enable httpd service
|
||||
ansible.builtin.service:
|
||||
name: httpd
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Add sample content to index.html
|
||||
ansible.builtin.blockinfile:
|
||||
path: /var/www/html/index.html
|
||||
create: yes
|
||||
block: |
|
||||
Welcome to XfusionCorp!
|
||||
This is Nautilus sample file, created using Ansible!
|
||||
Please do not modify this file manually!
|
||||
owner: apache
|
||||
group: apache
|
||||
mode: '0777'
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### Why `become: yes`
|
||||
|
||||
Every task here is privileged: installing a package writes to system directories and the RPM
|
||||
database, managing a systemd service requires root, and `/var/www/html/` is root-owned. The SSH
|
||||
users are unprivileged, so the whole play escalates with `become: yes` at the play level rather than
|
||||
repeating it per task.
|
||||
|
||||
Since validation runs the bare command with no `-K`, the sudo password must come from
|
||||
`ansible_become_pass` in the **existing** inventory. If the play fails with "Missing sudo password,"
|
||||
that variable is what's missing.
|
||||
|
||||
### Task 1 — install httpd
|
||||
|
||||
`ansible.builtin.yum` with **`state: present`** installs the package if absent and does nothing
|
||||
otherwise, making it idempotent (`changed` on first run, `ok` after). `state: latest` would upgrade
|
||||
on every run, which isn't what's asked.
|
||||
|
||||
### Task 2 — service up and running
|
||||
|
||||
`ansible.builtin.service` handles both halves of the requirement:
|
||||
|
||||
- **`state: started`** — ensures httpd is running **right now**.
|
||||
- **`enabled: yes`** — ensures it starts automatically **on boot**.
|
||||
|
||||
These are independent settings; "up and running" is satisfied by `started`, and `enabled` makes it
|
||||
durable across reboots. Including both is the standard, complete answer.
|
||||
|
||||
### Task 3 — `blockinfile` with default markers
|
||||
|
||||
`blockinfile` inserts (or updates) a **block** of text delimited by marker comments, so the block can
|
||||
be managed idempotently on re-runs — Ansible finds its own markers and replaces the content between
|
||||
them rather than appending duplicates.
|
||||
|
||||
- **No `marker:` parameter is specified**, per note (ii). Omitting it uses the default,
|
||||
`# {mark} ANSIBLE MANAGED BLOCK`, which produces `# BEGIN ANSIBLE MANAGED BLOCK` and
|
||||
`# END ANSIBLE MANAGED BLOCK` around your text. Supplying a custom or empty marker would violate
|
||||
the requirement — and an empty marker would break idempotency, since Ansible could no longer locate
|
||||
its block.
|
||||
|
||||
- **`create: yes`** — creates `/var/www/html/index.html` if it doesn't exist. A fresh httpd install
|
||||
typically has no `index.html`, and without this flag `blockinfile` would fail with "Destination
|
||||
does not exist."
|
||||
|
||||
- **`block: |`** — the literal block scalar preserves the text **exactly**, including line breaks.
|
||||
Note the double space in `This is Nautilus sample file` — that's reproduced verbatim from the
|
||||
requirement, since validation may compare the content character for character.
|
||||
|
||||
- **`owner: apache` / `group: apache`** — the required ownership. `apache` is the user httpd runs as
|
||||
on RHEL/CentOS-family systems, and the account exists only **after** the httpd package is
|
||||
installed — which is why the install task must come first. Reordering would fail with "chown
|
||||
failed: failed to look up user apache."
|
||||
|
||||
- **`mode: '0777'`** — quoted so YAML parses it as a string. Unquoted octal (`0777`) is a classic
|
||||
misparse that silently yields the wrong permissions.
|
||||
|
||||
### Ordering matters
|
||||
|
||||
The three tasks form a dependency chain: **install** creates the `apache` user and
|
||||
`/var/www/html/`; **service** brings httpd up; **blockinfile** then writes into a directory that
|
||||
exists and assigns ownership to a user that exists. Ansible runs tasks top to bottom, so this order
|
||||
is the correctness requirement, not just style.
|
||||
|
||||
### `hosts: all`
|
||||
|
||||
The inventory holds the app servers, so `all` targets exactly them and can't break if the group name
|
||||
and the playbook's `hosts:` value drift apart.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/ansible
|
||||
|
||||
# Connectivity first
|
||||
ansible -i inventory all -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# httpd installed and running
|
||||
ansible -i inventory all -b -m command -a "systemctl is-active httpd"
|
||||
|
||||
# File content, ownership, and permissions
|
||||
ansible -i inventory all -b -m command -a "ls -l /var/www/html/index.html"
|
||||
ansible -i inventory all -b -m command -a "cat /var/www/html/index.html"
|
||||
```
|
||||
|
||||
Expected — the playbook finishing with `failed=0`; `systemctl is-active httpd` returning `active`
|
||||
on each server; `ls -l` showing `-rwxrwxrwx` with `apache apache`; and `cat` showing the three lines
|
||||
wrapped in `# BEGIN ANSIBLE MANAGED BLOCK` / `# END ANSIBLE MANAGED BLOCK`.
|
||||
|
||||
> Those BEGIN/END marker lines are **expected and correct** — they're the default markers required by
|
||||
> note (ii). A "failed to look up user apache" error means the blockinfile task ran before httpd was
|
||||
> installed. "Missing sudo password" means the inventory needs `ansible_become_pass`.
|
||||
162
100 - days of devops/devops-89.md
Normal file
162
100 - days of devops/devops-89.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Assignment
|
||||
|
||||
Developers are looking for dependencies to be installed and run on Nautilus app servers in Stratos DC. They have shared some requirements with the DevOps team. Because we are now managing packages installation and services management using Ansible, some playbooks need to be created and tested. As per details mentioned below please complete the task:
|
||||
|
||||
a. On jump host create an Ansible playbook /home/thor/ansible/playbook.yml and configure it to install httpd on all app servers.
|
||||
b. After installation make sure to start and enable httpd service on all app servers.
|
||||
c. The inventory /home/thor/ansible/inventory is already there on jump host.
|
||||
d. Make sure user thor should be able to run the playbook on jump host.
|
||||
|
||||
Note: Validation will try to run playbook using command ansible-playbook -i inventory playbook.yml so please make sure playbook works this way, without passing any extra arguments.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Playbook — install and enable httpd on all App Servers
|
||||
|
||||
Create `/home/thor/ansible/playbook.yml` so `ansible-playbook -i inventory playbook.yml` installs
|
||||
httpd and brings the service up on all app servers — with **no extra arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredoc
|
||||
> below writes the playbook.
|
||||
|
||||
## Step 0 — The inventory already exists; leave it alone
|
||||
|
||||
Requirement (c) confirms the inventory is already in place. **Don't recreate or overwrite it** —
|
||||
just verify its contents:
|
||||
|
||||
```bash
|
||||
cat /home/thor/ansible/inventory
|
||||
```
|
||||
|
||||
Confirm it lists the app servers with their connection variables. If it lacks
|
||||
`ansible_become_pass` and the app-server users need a sudo password, add that per host — the
|
||||
playbook requires privilege escalation and you can't pass `-K` at runtime.
|
||||
|
||||
## Step 1 — Playbook
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Install and enable httpd on all app servers
|
||||
hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Install httpd package
|
||||
ansible.builtin.yum:
|
||||
name: httpd
|
||||
state: present
|
||||
|
||||
- name: Start and enable httpd service
|
||||
ansible.builtin.service:
|
||||
name: httpd
|
||||
state: started
|
||||
enabled: yes
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 2 — Ensure `thor` can run it (requirement d)
|
||||
|
||||
```bash
|
||||
ls -l /home/thor/ansible/
|
||||
```
|
||||
|
||||
Creating the playbook with the heredoc above (as `thor`) already gives correct ownership. If the
|
||||
directory was pre-created by another user and `thor` can't read the files, fix it:
|
||||
|
||||
```bash
|
||||
sudo chown -R thor:thor /home/thor/ansible
|
||||
chmod 644 /home/thor/ansible/playbook.yml
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### Why `become: yes`
|
||||
|
||||
Both tasks are privileged: installing a package writes to system directories and the RPM database,
|
||||
and managing a systemd service requires root. The SSH users on the app servers are unprivileged, so
|
||||
the play escalates once at the **play level** rather than repeating `become` on each task.
|
||||
|
||||
Because validation runs the bare command with no `-K`, the sudo password must come from
|
||||
`ansible_become_pass` in the existing inventory. "Missing sudo password" at runtime means that
|
||||
variable is absent or wrong — and it's the most common failure on this task, since there's no way to
|
||||
supply it on the command line.
|
||||
|
||||
### Task 1 — install httpd
|
||||
|
||||
`ansible.builtin.yum` with **`state: present`** installs the package if it's missing and does nothing
|
||||
if it's already there. That makes the task **idempotent**: the first run reports `changed`,
|
||||
subsequent runs report `ok`. `state: latest` would instead upgrade on every run, which isn't what
|
||||
"install" asks for.
|
||||
|
||||
These app servers are RHEL/CentOS-family, so `yum` is the correct package module. (On modern
|
||||
RHEL 8+ the `dnf` module is its successor, and Ansible's `yum` module delegates to dnf where
|
||||
appropriate — so `yum` works regardless.)
|
||||
|
||||
### Task 2 — start *and* enable
|
||||
|
||||
Requirement (b) has two distinct halves, and `ansible.builtin.service` covers both:
|
||||
|
||||
| Option | Effect |
|
||||
|--------|--------|
|
||||
| `state: started` | httpd is running **right now**. |
|
||||
| `enabled: yes` | httpd starts automatically **on boot**. |
|
||||
|
||||
These are independent — a service can be running but not enabled (dies on reboot), or enabled but
|
||||
not currently started. "Start and enable" requires both, so both are set.
|
||||
|
||||
### Ordering matters
|
||||
|
||||
The install task must precede the service task: you can't start a service whose unit file doesn't
|
||||
exist yet. Ansible executes tasks top to bottom, so this ordering is a correctness requirement, not
|
||||
just style. Reversing them would fail with "Could not find the requested service httpd."
|
||||
|
||||
### `hosts: all`
|
||||
|
||||
The inventory contains the app servers, so `all` targets exactly them — and it can't break if the
|
||||
inventory's group name and the playbook's `hosts:` value ever drift apart.
|
||||
|
||||
### Requirement (d) — "thor should be able to run the playbook"
|
||||
|
||||
Validation runs as `thor`, so `thor` must be able to **read** the playbook and inventory. Writing the
|
||||
file via heredoc as `thor` satisfies this automatically; Step 2 is a safety check for the case where
|
||||
the directory was pre-created with root ownership, which would fail validation before Ansible even
|
||||
starts.
|
||||
|
||||
### Host key checking
|
||||
|
||||
If a first-time SSH connection trips on host-key verification, add an `ansible.cfg` beside the
|
||||
playbook — safest because it needs no extra command-line arguments:
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/ansible.cfg <<'EOF'
|
||||
[defaults]
|
||||
host_key_checking = False
|
||||
EOF
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/ansible
|
||||
|
||||
# Connectivity first
|
||||
ansible -i inventory all -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# httpd installed
|
||||
ansible -i inventory all -b -m command -a "rpm -q httpd"
|
||||
|
||||
# Running AND enabled
|
||||
ansible -i inventory all -b -m command -a "systemctl is-active httpd"
|
||||
ansible -i inventory all -b -m command -a "systemctl is-enabled httpd"
|
||||
```
|
||||
|
||||
Expected — `ping` returning `SUCCESS` on every server; the playbook finishing with `failed=0`;
|
||||
`rpm -q httpd` printing an installed version; `is-active` returning `active`; and `is-enabled`
|
||||
returning `enabled`.
|
||||
|
||||
> Check **both** `is-active` and `is-enabled` — passing only one means half the requirement was met.
|
||||
> "Could not find the requested service httpd" ⇒ task order is wrong. "Missing sudo password" ⇒ the
|
||||
> inventory needs `ansible_become_pass`.
|
||||
209
100 - days of devops/devops-90.md
Normal file
209
100 - days of devops/devops-90.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Assignment
|
||||
|
||||
There are some files that need to be created on all app servers in Stratos DC. The Nautilus DevOps team want these files to be owned by user root only however, they also want that the app specific user to have a set of permissions on these files. All tasks must be done using Ansible only, so they need to create a playbook. Below you can find more information about the task.
|
||||
|
||||
Create a playbook named playbook.yml under /home/thor/ansible directory on jump host, an inventory file is already present under /home/thor/ansible directory on Jump Server itself.
|
||||
|
||||
Create an empty file blog.txt under /opt/data/ directory on app server 1. Set some acl properties for this file. Using acl provide read '(r)' permissions to group tony (i.e entity is tony and etype is group).
|
||||
|
||||
Create an empty file story.txt under /opt/data/ directory on app server 2. Set some acl properties for this file. Using acl provide read + write '(rw)' permissions to user steve (i.e entity is steve and etype is user).
|
||||
|
||||
Create an empty file media.txt under /opt/data/ on app server 3. Set some acl properties for this file. Using acl provide read + write '(rw)' permissions to group banner (i.e entity is banner and etype is group).
|
||||
|
||||
Note: Validation will try to run the playbook using command ansible-playbook -i inventory playbook.yml so please make sure the playbook works this way, without passing any extra arguments.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Playbook — root-owned files with per-user ACLs
|
||||
|
||||
Create `/home/thor/ansible/playbook.yml` so `ansible-playbook -i inventory playbook.yml` creates a
|
||||
root-owned file on each app server and grants an app-specific user or group extra access via ACLs —
|
||||
with **no extra arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredoc
|
||||
> below writes the playbook.
|
||||
|
||||
## Step 0 — Check the existing inventory
|
||||
|
||||
The inventory is already present. **Don't overwrite it** — but confirm the hostnames, since the
|
||||
playbook targets each server by name:
|
||||
|
||||
```bash
|
||||
cat /home/thor/ansible/inventory
|
||||
```
|
||||
|
||||
You need `stapp01`, `stapp02`, `stapp03` (match the playbook's `hosts:` values to whatever names it
|
||||
uses). Also confirm `ansible_become_pass` is present, since the play requires root and you can't
|
||||
pass `-K`.
|
||||
|
||||
## Step 1 — Playbook
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Configure blog.txt on App Server 1
|
||||
hosts: stapp01
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Create /opt/data/blog.txt owned by root
|
||||
ansible.builtin.file:
|
||||
path: /opt/data/blog.txt
|
||||
state: touch
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Grant read permission to group tony
|
||||
acl:
|
||||
path: /opt/data/blog.txt
|
||||
entity: tony
|
||||
etype: group
|
||||
permissions: r
|
||||
state: present
|
||||
|
||||
- name: Configure story.txt on App Server 2
|
||||
hosts: stapp02
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Create /opt/data/story.txt owned by root
|
||||
ansible.builtin.file:
|
||||
path: /opt/data/story.txt
|
||||
state: touch
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Grant read+write permission to user steve
|
||||
acl:
|
||||
path: /opt/data/story.txt
|
||||
entity: steve
|
||||
etype: user
|
||||
permissions: rw
|
||||
state: present
|
||||
|
||||
- name: Configure media.txt on App Server 3
|
||||
hosts: stapp03
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Create /opt/data/media.txt owned by root
|
||||
ansible.builtin.file:
|
||||
path: /opt/data/media.txt
|
||||
state: touch
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Grant read+write permission to group banner
|
||||
acl:
|
||||
path: /opt/data/media.txt
|
||||
entity: banner
|
||||
etype: group
|
||||
permissions: rw
|
||||
state: present
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The core idea: root ownership + ACLs for everyone else
|
||||
|
||||
This is exactly the problem ACLs solve. Traditional Unix permissions give a file **one** owner and
|
||||
**one** group — so if `root` must own the file, there's no way to also grant `tony` or `steve`
|
||||
specific access through `chmod` alone without opening it to "other" (everyone).
|
||||
|
||||
**ACLs** add supplementary entries on top of the standard permission bits, letting you name
|
||||
additional users or groups and give each its own rights. So:
|
||||
|
||||
- `owner: root` / `group: root` satisfies "owned by user root only"
|
||||
- the `acl` entry satisfies "app specific user should have a set of permissions"
|
||||
|
||||
Both requirements hold simultaneously, which plain permissions couldn't achieve.
|
||||
|
||||
### Why three separate plays
|
||||
|
||||
Each server needs a different **filename**, **entity**, **etype**, and **permission set** — four
|
||||
things varying at once with no shared pattern. Three plays scoped with `hosts: stapp01` /
|
||||
`stapp02` / `stapp03` express that directly. A single play with
|
||||
`when: inventory_hostname == "stapp01"` conditionals would work too, but needs six conditional tasks
|
||||
and buries the intent.
|
||||
|
||||
One playbook file can hold multiple plays; they execute top to bottom, each against its own host set.
|
||||
|
||||
### The `file` task
|
||||
|
||||
- **`state: touch`** — creates the file if absent, leaving it empty ("create an empty file").
|
||||
- **`owner: root` / `group: root`** — the explicit ownership requirement. Setting ownership requires
|
||||
root privileges (`chown` is privileged), which the play already has via `become: yes`.
|
||||
|
||||
Note `state: touch` bumps timestamps each run, so re-runs report `changed` rather than `ok`. Fine
|
||||
here.
|
||||
|
||||
### The `acl` module
|
||||
|
||||
The module maps directly onto `setfacl`:
|
||||
|
||||
| Parameter | Meaning | Values across the three servers |
|
||||
|-----------|---------|----------------------------------|
|
||||
| `path` | File to modify | `/opt/data/blog.txt`, `story.txt`, `media.txt` |
|
||||
| `entity` | **Who** the rule applies to | `tony`, `steve`, `banner` |
|
||||
| `etype` | Entity kind: `user`, `group`, `other`, `mask` | **group**, **user**, **group** |
|
||||
| `permissions` | Rights as `rwx` letters | **r**, **rw**, **rw** |
|
||||
| `state: present` | Ensure the entry exists | — |
|
||||
|
||||
Watch the pairing — servers 1 and 3 both use `etype: group` but with **different** permissions
|
||||
(`r` vs `rw`), while server 2 uses `etype: user`. Transposing `user` and `group` is the most common
|
||||
way to fail this task.
|
||||
|
||||
The shell equivalent for the first play is `setfacl -m g:tony:r /opt/data/blog.txt`; the module makes
|
||||
it declarative and idempotent.
|
||||
|
||||
### Module naming
|
||||
|
||||
The short name `acl` is used for compatibility. On modern Ansible the module lives in the
|
||||
`ansible.posix` collection and the short name routes there automatically when that collection is
|
||||
installed (standard in a full Ansible install). If you hit "couldn't resolve module/action 'acl'",
|
||||
switch to the fully-qualified name:
|
||||
|
||||
```yaml
|
||||
ansible.posix.acl:
|
||||
```
|
||||
|
||||
installing it if needed with `ansible-galaxy collection install ansible.posix`.
|
||||
|
||||
### Why `become: yes`
|
||||
|
||||
Three things here need root: creating files under the root-owned `/opt/data`, setting `owner`/`group`
|
||||
to root (`chown`), and applying ACLs with `setfacl`. The play escalates once at play level, and the
|
||||
sudo password comes from `ansible_become_pass` in the inventory since `-K` can't be passed.
|
||||
|
||||
### Prerequisite
|
||||
|
||||
ACL support needs the `acl` package (`setfacl`/`getfacl`) on the **managed nodes** and a filesystem
|
||||
mounted with ACL support. Both are standard on RHEL/CentOS-family systems. If a play fails with
|
||||
"setfacl not found," installing the `acl` package on the target resolves it.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/ansible
|
||||
|
||||
# Connectivity
|
||||
ansible -i inventory all -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# Ownership and ACLs per server
|
||||
ansible -i inventory stapp01 -b -m command -a "getfacl /opt/data/blog.txt"
|
||||
ansible -i inventory stapp02 -b -m command -a "getfacl /opt/data/story.txt"
|
||||
ansible -i inventory stapp03 -b -m command -a "getfacl /opt/data/media.txt"
|
||||
```
|
||||
|
||||
Expected — the playbook finishing with `failed=0` across all three plays, and `getfacl` output
|
||||
showing `# owner: root` / `# group: root` at the top plus:
|
||||
|
||||
- stapp01 `/opt/data/blog.txt` → `group:tony:r--`
|
||||
- stapp02 `/opt/data/story.txt` → `user:steve:rw-`
|
||||
- stapp03 `/opt/data/media.txt` → `group:banner:rw-`
|
||||
|
||||
> `getfacl` conveniently shows **both** requirements at once — the owner/group header lines and the
|
||||
> supplementary ACL entries. Check the `user:` vs `group:` prefix carefully. "Missing sudo password"
|
||||
> ⇒ inventory needs `ansible_become_pass`. "No hosts matched" ⇒ inventory hostnames don't match the
|
||||
> `hosts:` values.
|
||||
200
100 - days of devops/devops-91.md
Normal file
200
100 - days of devops/devops-91.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team want to install and set up a simple httpd web server on all app servers in Stratos DC. They also want to deploy a sample web page using Ansible. Therefore, write the required playbook to complete this task as per details mentioned below.
|
||||
|
||||
|
||||
We already have an inventory file under /home/thor/ansible directory on jump host. Write a playbook playbook.yml under /home/thor/ansible directory on jump host itself. Using the playbook perform below given tasks:
|
||||
|
||||
|
||||
Install httpd web server on all app servers, and make sure its service is up and running.
|
||||
|
||||
|
||||
Create a file /var/www/html/index.html with content:
|
||||
|
||||
|
||||
This is a Nautilus sample file, created using Ansible!
|
||||
|
||||
|
||||
Using lineinfile Ansible module add some more content in /var/www/html/index.html file. Below is the content:
|
||||
|
||||
Welcome to Nautilus Group!
|
||||
|
||||
|
||||
Also make sure this new line is added at the top of the file.
|
||||
|
||||
|
||||
The /var/www/html/index.html file's user and group owner should be apache on all app servers.
|
||||
|
||||
|
||||
The /var/www/html/index.html file's permissions should be 0655 on all app servers.
|
||||
|
||||
|
||||
Note: Validation will try to run the playbook using command ansible-playbook -i inventory playbook.yml so please make sure the playbook works this way without passing any extra arguments.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Playbook — httpd + sample page with `lineinfile`
|
||||
|
||||
Create `/home/thor/ansible/playbook.yml` so `ansible-playbook -i inventory playbook.yml` installs
|
||||
httpd, starts it, and builds an `index.html` with a line prepended at the top — with **no extra
|
||||
arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredoc
|
||||
> below writes the playbook.
|
||||
|
||||
## Step 0 — The inventory already exists; leave it alone
|
||||
|
||||
```bash
|
||||
cat /home/thor/ansible/inventory
|
||||
```
|
||||
|
||||
Confirm it lists the app servers with their connection variables, and that
|
||||
`ansible_become_pass` is present — the play needs root and you can't pass `-K`.
|
||||
|
||||
## Step 1 — Playbook
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Install httpd and deploy sample web page
|
||||
hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Install httpd package
|
||||
ansible.builtin.yum:
|
||||
name: httpd
|
||||
state: present
|
||||
|
||||
- name: Start and enable httpd service
|
||||
ansible.builtin.service:
|
||||
name: httpd
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Create index.html with sample content
|
||||
ansible.builtin.copy:
|
||||
dest: /var/www/html/index.html
|
||||
content: |
|
||||
This is a Nautilus sample file, created using Ansible!
|
||||
owner: apache
|
||||
group: apache
|
||||
mode: '0655'
|
||||
|
||||
- name: Add welcome line at the top of index.html
|
||||
ansible.builtin.lineinfile:
|
||||
path: /var/www/html/index.html
|
||||
line: 'Welcome to Nautilus Group!'
|
||||
insertbefore: BOF
|
||||
owner: apache
|
||||
group: apache
|
||||
mode: '0655'
|
||||
EOF
|
||||
```
|
||||
|
||||
Resulting file content:
|
||||
|
||||
```
|
||||
Welcome to Nautilus Group!
|
||||
This is a Nautilus sample file, created using Ansible!
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### Task order is a hard dependency
|
||||
|
||||
The four tasks must run in this sequence:
|
||||
|
||||
1. **Install httpd** — this creates the `apache` user *and* the `/var/www/html/` directory. Both are
|
||||
needed by later tasks.
|
||||
2. **Start/enable the service** — httpd must exist before it can be managed.
|
||||
3. **Create the file** — the directory now exists and `apache` is a valid owner.
|
||||
4. **Prepend the line** — the file must exist before `lineinfile` can modify it.
|
||||
|
||||
Reordering breaks things concretely: a `copy` before the install fails with "Destination directory
|
||||
does not exist" or "failed to look up user apache"; a `lineinfile` before the `copy` has nothing to
|
||||
insert into.
|
||||
|
||||
### Task 1 & 2 — install and run
|
||||
|
||||
- **`yum` with `state: present`** — installs if missing, no-ops otherwise (idempotent: `changed`
|
||||
first run, `ok` after). `state: latest` would upgrade on every run, which isn't what's asked.
|
||||
- **`service` with `state: started` *and* `enabled: yes`** — two independent settings.
|
||||
`started` = running now; `enabled` = starts on boot. "Up and running" needs `started`; `enabled`
|
||||
makes it survive reboots. Both are standard for this requirement.
|
||||
|
||||
### Task 3 — `copy` with inline `content`
|
||||
|
||||
Using `copy` with the **`content:`** parameter writes a literal string to `dest` — no source file on
|
||||
the controller needed. The `|` block scalar preserves the text exactly and appends a trailing
|
||||
newline, so the next task's line lands cleanly above it.
|
||||
|
||||
(`copy` also accepts `src:` for pushing an existing file; `content:` is the right choice when the
|
||||
text is defined inline, as here.)
|
||||
|
||||
### Task 4 — `lineinfile` with `insertbefore: BOF`
|
||||
|
||||
This is the crux of requirement 3. `lineinfile` ensures a **single line** is present in a file:
|
||||
|
||||
- **`line: 'Welcome to Nautilus Group!'`** — the exact text to guarantee.
|
||||
- **`insertbefore: BOF`** — `BOF` is a special value meaning **Beginning Of File**. It places the
|
||||
line as the very first line, which is precisely "added at the top." Without it, `lineinfile`
|
||||
appends to the end by default — the file would still contain both lines, but in the wrong order,
|
||||
and validation would fail.
|
||||
|
||||
(The mirror value is `insertafter: EOF` for the end of the file.)
|
||||
|
||||
`lineinfile` is also **idempotent**: on re-runs it finds the line already present and reports `ok`
|
||||
rather than inserting a duplicate.
|
||||
|
||||
### `copy` vs `lineinfile` — why both
|
||||
|
||||
They do different jobs. `copy` establishes the file's whole content from scratch (requirement 2);
|
||||
`lineinfile` surgically inserts one line into an existing file (requirement 3). The task explicitly
|
||||
asks for `lineinfile` for the second piece, so this two-step approach matches the requirements
|
||||
directly rather than just writing both lines in one `copy`.
|
||||
|
||||
### Ownership and permissions
|
||||
|
||||
- **`owner: apache` / `group: apache`** — the `apache` account exists only *after* the httpd package
|
||||
is installed, which is why task 1 must come first.
|
||||
- **`mode: '0655'`** — **quoted**. Unquoted octal like `0655` is a classic YAML misparse that
|
||||
silently produces the wrong permissions. Note this is `0655`, not the more familiar `0644` or
|
||||
`0755` — copy it exactly.
|
||||
|
||||
Both are set on the `copy` **and** the `lineinfile` task. Setting them on the final task is what
|
||||
guarantees the end state, since `lineinfile` rewrites the file; specifying them on both makes the
|
||||
result deterministic regardless of which task last touched the file.
|
||||
|
||||
### `become: yes`
|
||||
|
||||
Installing packages, managing systemd services, writing under root-owned `/var/www/html/`, and
|
||||
running `chown` all require root. The play escalates once at play level, with the sudo password
|
||||
coming from `ansible_become_pass` in the inventory.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/ansible
|
||||
|
||||
# Connectivity
|
||||
ansible -i inventory all -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# Service running and enabled
|
||||
ansible -i inventory all -b -m command -a "systemctl is-active httpd"
|
||||
ansible -i inventory all -b -m command -a "systemctl is-enabled httpd"
|
||||
|
||||
# Content order, ownership, permissions
|
||||
ansible -i inventory all -b -m command -a "cat /var/www/html/index.html"
|
||||
ansible -i inventory all -b -m command -a "ls -l /var/www/html/index.html"
|
||||
```
|
||||
|
||||
Expected — playbook `failed=0`; `is-active` → `active` and `is-enabled` → `enabled`; `cat` showing
|
||||
**`Welcome to Nautilus Group!` on the first line** followed by the sample-file line; and `ls -l`
|
||||
showing `-rw-r-xr-x` (0655) with `apache apache`.
|
||||
|
||||
> The line **order** is the thing to check most carefully — if the welcome line appears at the
|
||||
> bottom, `insertbefore: BOF` is missing or misspelled. "failed to look up user apache" ⇒ a file task
|
||||
> ran before the httpd install.
|
||||
189
100 - days of devops/devops-92.md
Normal file
189
100 - days of devops/devops-92.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Assignment
|
||||
|
||||
One of the Nautilus DevOps team members is working on to develop a role for httpd installation and configuration. Work is almost completed, however there is a requirement to add a jinja2 template for index.html file. Additionally, the relevant task needs to be added inside the role. The inventory file ~/ansible/inventory is already present on jump host that can be used. Complete the task as per details mentioned below:
|
||||
|
||||
|
||||
a. Update ~/ansible/playbook.yml playbook to run the httpd role on App Server 2.
|
||||
|
||||
|
||||
b. Create a jinja2 template index.html.j2 under /home/thor/ansible/role/httpd/templates/ directory and add a line This file was created using Ansible on <respective server> (for example This file was created using Ansible on stapp01 in case of App Server 1). Also please make sure not to hard code the server name inside the template. Instead, use inventory_hostname variable to fetch the correct value.
|
||||
|
||||
|
||||
c. Add a task inside /home/thor/ansible/role/httpd/tasks/main.yml to copy this template on App Server 2 under /var/www/html/index.html. Also make sure that /var/www/html/index.html file's permissions are 0755.
|
||||
|
||||
|
||||
d. The user/group owner of /var/www/html/index.html file must be respective sudo user of the server (for example tony in case of stapp01).
|
||||
|
||||
|
||||
Note: Validation will try to run the playbook using command ansible-playbook -i inventory playbook.yml so please make sure the playbook works this way without passing any extra arguments.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Role — jinja2 template for `index.html`
|
||||
|
||||
Add a jinja2 template to the existing `httpd` role and wire it up so
|
||||
`ansible-playbook -i inventory playbook.yml` deploys it to App Server 2 — with **no extra
|
||||
arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredocs
|
||||
> below write/append the files.
|
||||
|
||||
## Step 0 — Inspect what already exists
|
||||
|
||||
The role is partially built. Look before you touch anything:
|
||||
|
||||
```bash
|
||||
cat /home/thor/ansible/inventory
|
||||
cat /home/thor/ansible/playbook.yml
|
||||
cat /home/thor/ansible/role/httpd/tasks/main.yml
|
||||
ls -l /home/thor/ansible/role/httpd/
|
||||
```
|
||||
|
||||
Note the existing tasks in `main.yml` — **they must be preserved**. Also confirm the inventory has
|
||||
`stapp02` and an `ansible_become_pass`, since the role needs root and `-K` can't be passed.
|
||||
|
||||
## Step 1 — Update the playbook (a)
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Install and configure httpd on App Server 2
|
||||
hosts: stapp02
|
||||
become: yes
|
||||
roles:
|
||||
- role/httpd
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 2 — Create the jinja2 template (b)
|
||||
|
||||
```bash
|
||||
mkdir -p /home/thor/ansible/role/httpd/templates
|
||||
|
||||
cat > /home/thor/ansible/role/httpd/templates/index.html.j2 <<'EOF'
|
||||
This file was created using Ansible on {{ inventory_hostname }}
|
||||
EOF
|
||||
```
|
||||
|
||||
**The quoted `<<'EOF'` is essential here** — it stops the shell from touching `{{ inventory_hostname }}`
|
||||
so the literal jinja2 expression reaches the file intact.
|
||||
|
||||
## Step 3 — Append the task to the role (c, d)
|
||||
|
||||
```bash
|
||||
cat >> /home/thor/ansible/role/httpd/tasks/main.yml <<'EOF'
|
||||
|
||||
- name: Deploy index.html from jinja2 template
|
||||
ansible.builtin.template:
|
||||
src: index.html.j2
|
||||
dest: /var/www/html/index.html
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: '0755'
|
||||
EOF
|
||||
```
|
||||
|
||||
Note **`>>`** (append), not `>` (overwrite) — this preserves the role's existing install/service
|
||||
tasks. Verify afterwards:
|
||||
|
||||
```bash
|
||||
cat /home/thor/ansible/role/httpd/tasks/main.yml
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The playbook and the role path
|
||||
|
||||
- **`hosts: stapp02`** — requirement (a) scopes the run to App Server 2 only.
|
||||
- **`roles: - role/httpd`** — the role lives at `/home/thor/ansible/role/httpd`. Ansible's default
|
||||
search path is `./roles/`, but this directory is named `role` (singular), so the relative path
|
||||
`role/httpd` is given explicitly. Ansible resolves it relative to the **playbook's directory**,
|
||||
which is exactly where it sits. Writing just `httpd` would fail with "the role 'httpd' was not
|
||||
found."
|
||||
- **`become: yes`** — the role installs packages, manages a service, and writes under root-owned
|
||||
`/var/www/html/`. All privileged.
|
||||
|
||||
### The jinja2 template — `inventory_hostname`
|
||||
|
||||
Requirement (b) forbids hardcoding the server name. **`{{ inventory_hostname }}`** is an Ansible
|
||||
*magic variable* holding the name of the host **as written in the inventory** — so it evaluates to
|
||||
`stapp02` when the play runs against App Server 2, `stapp01` on App Server 1, and so on.
|
||||
|
||||
That's why the file is a **template** (`.j2`) rather than a static file: `template` renders jinja2
|
||||
expressions at deploy time, substituting the correct value per host. A `copy` task would transfer
|
||||
the literal text `{{ inventory_hostname }}` unrendered — the exact mistake the requirement is
|
||||
guarding against.
|
||||
|
||||
The rendered result on App Server 2:
|
||||
|
||||
```
|
||||
This file was created using Ansible on stapp02
|
||||
```
|
||||
|
||||
> Related variables worth distinguishing: `inventory_hostname` is the inventory's name for the host
|
||||
> (what's needed here), while `ansible_hostname` is the machine's actual short hostname discovered by
|
||||
> fact-gathering. They often match, but the requirement names `inventory_hostname` explicitly.
|
||||
|
||||
### The template task
|
||||
|
||||
- **`ansible.builtin.template`** — renders jinja2 then copies. The counterpart to `copy` for dynamic
|
||||
content.
|
||||
- **`src: index.html.j2`** — a **bare filename**, no path. Inside a role, the `template` module
|
||||
automatically searches the role's `templates/` directory, so `templates/index.html.j2` is found
|
||||
without qualification. That's a role convention worth knowing — it's why the directory name matters.
|
||||
- **`dest: /var/www/html/index.html`** — the deployed path.
|
||||
- **`mode: '0755'`** — quoted so YAML parses it as a string; unquoted octal is a classic silent
|
||||
misparse.
|
||||
|
||||
### Requirement (d) — owner via `{{ ansible_user }}`
|
||||
|
||||
The owner must be "the respective sudo user of the server" — `tony` on stapp01, `steve` on stapp02,
|
||||
`banner` on stapp03. Those are precisely the `ansible_user` values already defined per host in the
|
||||
inventory, so:
|
||||
|
||||
```yaml
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
```
|
||||
|
||||
resolves per host automatically — `steve` here, since the play targets stapp02. No conditionals
|
||||
needed, and it stays correct if the role is later run against other servers. (This is the same
|
||||
pattern as the earlier per-host ownership task.)
|
||||
|
||||
`group` uses the same value because Linux creates a matching primary group per user by default
|
||||
(`steve` → group `steve`).
|
||||
|
||||
### Why appending matters
|
||||
|
||||
`tasks/main.yml` already contains the role's install and service tasks — the work "almost completed"
|
||||
by the team member. Overwriting it with `>` would delete those and the role would no longer install
|
||||
httpd, so the template task would land in a directory that doesn't exist. `>>` adds the new task to
|
||||
the end, which is also the correct **order**: httpd must be installed (creating `/var/www/html/`)
|
||||
before the template is written into it.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/ansible
|
||||
|
||||
# Existing role tasks still present, new task appended at the end
|
||||
cat role/httpd/tasks/main.yml
|
||||
|
||||
# Connectivity
|
||||
ansible -i inventory stapp02 -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# Rendered content, ownership, permissions
|
||||
ansible -i inventory stapp02 -b -m command -a "cat /var/www/html/index.html"
|
||||
ansible -i inventory stapp02 -b -m command -a "ls -l /var/www/html/index.html"
|
||||
```
|
||||
|
||||
Expected — playbook `failed=0`; `cat` printing
|
||||
**`This file was created using Ansible on stapp02`** (rendered, *not* the literal `{{ ... }}`); and
|
||||
`ls -l` showing `-rwxr-xr-x` with owner/group `steve steve`.
|
||||
|
||||
> If the file contains a literal `{{ inventory_hostname }}`, the task used `copy` instead of
|
||||
> `template`, or the shell expanded the heredoc — recheck that Step 2 used the **quoted** `<<'EOF'`.
|
||||
> "The role 'httpd' was not found" ⇒ the `role/httpd` relative path is wrong.
|
||||
182
100 - days of devops/devops-93.md
Normal file
182
100 - days of devops/devops-93.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team had a discussion about, how they can train different team members to use Ansible for different automation tasks. There are numerous ways to perform a particular task using Ansible, but we want to utilize each aspect that Ansible offers. The team wants to utilise Ansible's conditionals to perform the following task:
|
||||
|
||||
|
||||
An inventory file is already placed under /home/thor/ansible directory on jump host, with all the Stratos DC app servers included.
|
||||
|
||||
|
||||
Create a playbook /home/thor/ansible/playbook.yml and make sure to use Ansible's when conditionals statements to perform the below given tasks.
|
||||
|
||||
|
||||
Copy blog.txt file present under /usr/src/data directory on jump host to App Server 1 under /opt/data directory. Its user and group owner must be user tony and its permissions must be 0644 .
|
||||
|
||||
|
||||
Copy story.txt file present under /usr/src/data directory on jump host to App Server 2 under /opt/data directory. Its user and group owner must be user steve and its permissions must be 0644 .
|
||||
|
||||
|
||||
Copy media.txt file present under /usr/src/data directory on jump host to App Server 3 under /opt/data directory. Its user and group owner must be user banner and its permissions must be 0644.
|
||||
|
||||
|
||||
NOTE: You can use ansible_nodename variable from gathered facts with when condition. Additionally, please make sure you are running the play for all hosts i.e use - hosts: all.
|
||||
|
||||
|
||||
Note: Validation will try to run the playbook using command ansible-playbook -i inventory playbook.yml, so please make sure the playbook works this way without passing any extra arguments.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Playbook — per-server copies using `when` conditionals
|
||||
|
||||
Create `/home/thor/ansible/playbook.yml` so `ansible-playbook -i inventory playbook.yml` copies a
|
||||
different file to each app server, selected with **`when` conditionals** on a single
|
||||
`hosts: all` play — with **no extra arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredoc
|
||||
> below writes the playbook.
|
||||
|
||||
## Step 0 — Confirm the inventory hostnames
|
||||
|
||||
The conditionals branch on the hostnames as written in your inventory, so read them first:
|
||||
|
||||
```bash
|
||||
cat /home/thor/ansible/inventory
|
||||
```
|
||||
|
||||
Use exactly the names it lists (`stapp01`, `stapp02`, `stapp03` in the standard setup) in the
|
||||
`when:` lines below. Also confirm `ansible_become_pass` is present, since the play needs root and
|
||||
`-K` can't be passed.
|
||||
|
||||
## Step 1 — Playbook
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Copy files to app servers using conditionals
|
||||
hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Copy blog.txt to App Server 1
|
||||
ansible.builtin.copy:
|
||||
src: /usr/src/data/blog.txt
|
||||
dest: /opt/data/blog.txt
|
||||
owner: tony
|
||||
group: tony
|
||||
mode: '0644'
|
||||
when: inventory_hostname == "stapp01"
|
||||
|
||||
- name: Copy story.txt to App Server 2
|
||||
ansible.builtin.copy:
|
||||
src: /usr/src/data/story.txt
|
||||
dest: /opt/data/story.txt
|
||||
owner: steve
|
||||
group: steve
|
||||
mode: '0644'
|
||||
when: inventory_hostname == "stapp02"
|
||||
|
||||
- name: Copy media.txt to App Server 3
|
||||
ansible.builtin.copy:
|
||||
src: /usr/src/data/media.txt
|
||||
dest: /opt/data/media.txt
|
||||
owner: banner
|
||||
group: banner
|
||||
mode: '0644'
|
||||
when: inventory_hostname == "stapp03"
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The conditional pattern
|
||||
|
||||
With **`hosts: all`**, every task is evaluated against **every** host. The `when:` clause is what
|
||||
narrows each one:
|
||||
|
||||
- On stapp01, task 1's condition is true → it **runs**; tasks 2 and 3 are false → **skipped**.
|
||||
- On stapp02, only task 2 runs. On stapp03, only task 3.
|
||||
|
||||
So the play visits all three servers, and each executes exactly the one task meant for it. The
|
||||
`PLAY RECAP` will show roughly `ok=2 changed=1 skipped=2` per host — **skipped tasks are expected
|
||||
and correct here**, not a failure.
|
||||
|
||||
This is the opposite structural choice from writing three separate plays (`hosts: stapp01`, etc.).
|
||||
Both produce the same end state; the task explicitly asks for the conditional form, which is what
|
||||
you'd reach for when the differences are small and you want one play covering the fleet.
|
||||
|
||||
### Why `inventory_hostname`
|
||||
|
||||
`inventory_hostname` is a **magic variable** holding the host's name **exactly as written in the
|
||||
inventory file**. That makes it the most reliable thing to branch on:
|
||||
|
||||
- You can read its value directly (`cat inventory`) — no guessing, no remote lookup.
|
||||
- It doesn't depend on fact gathering.
|
||||
- It can't drift from what the inventory says, because it *is* what the inventory says.
|
||||
|
||||
**The alternative — `ansible_nodename`:** the task's note mentions it, and it also works, but it's a
|
||||
**gathered fact** reporting whatever name the operating system is configured with. That's often a
|
||||
fully-qualified domain name rather than the short one, and the exact value depends on how the lab's
|
||||
hosts are configured. Comparing it against the wrong string matches nothing, every task silently
|
||||
skips, and the playbook reports **success with no work done** — a failure mode that looks like a
|
||||
pass until you inspect the files.
|
||||
|
||||
If you prefer that route, get the real values first rather than assuming them:
|
||||
|
||||
```bash
|
||||
ansible -i inventory all -m setup -a "filter=ansible_nodename"
|
||||
```
|
||||
|
||||
then paste the exact output into the conditions:
|
||||
|
||||
```yaml
|
||||
when: ansible_nodename == "<exact value printed for stapp01>"
|
||||
```
|
||||
|
||||
Either variable satisfies the requirement — the task says you *can* use `ansible_nodename`, not that
|
||||
you must, and validation checks the resulting files rather than which variable you branched on.
|
||||
`inventory_hostname` is simply the version with nothing left to verify.
|
||||
|
||||
### The `copy` task
|
||||
|
||||
- **`src: /usr/src/data/blog.txt`** — a path on the **control node** (the jump host). `copy` reads
|
||||
from the controller by default, which is exactly "copy from jump host to app server." (Copying
|
||||
between two paths on the *remote* machine would need `remote_src: yes` — not the case here.)
|
||||
- **`dest: /opt/data/blog.txt`** — the explicit target path, filename included. Clearer than relying
|
||||
on `dest: /opt/data/` directory-expansion.
|
||||
- **`owner` / `group`** — the required per-server user. Hardcoded here because each task is already
|
||||
pinned to one host by its `when:`, so the literal value is unambiguous and matches the requirement
|
||||
text directly.
|
||||
- **`mode: '0644'`** — **quoted**. Unquoted octal like `0644` is a classic YAML misparse that
|
||||
silently yields wrong permissions.
|
||||
|
||||
### Why `become: yes`
|
||||
|
||||
`/opt/data` is root-owned, so writing there requires escalation — and setting `owner`/`group` runs
|
||||
`chown`, which is privileged regardless. The play escalates once at play level, with the sudo
|
||||
password supplied by `ansible_become_pass` from the inventory.
|
||||
|
||||
> If `/opt/data` doesn't already exist on the targets, `copy` fails with "Destination directory does
|
||||
> not exist" — it won't create missing parents. Add a `file`/`state: directory` task before the
|
||||
> copies if you hit that.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/ansible
|
||||
|
||||
# Connectivity
|
||||
ansible -i inventory all -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# Each file, ownership and permissions
|
||||
ansible -i inventory stapp01 -b -m command -a "ls -l /opt/data/blog.txt"
|
||||
ansible -i inventory stapp02 -b -m command -a "ls -l /opt/data/story.txt"
|
||||
ansible -i inventory stapp03 -b -m command -a "ls -l /opt/data/media.txt"
|
||||
```
|
||||
|
||||
Expected — playbook `failed=0` with `skipped=2` on each host (normal for this pattern), and `ls -l`
|
||||
showing `-rw-r--r--` with `tony tony`, `steve steve`, and `banner banner` respectively.
|
||||
|
||||
> **If every task shows `skipped` and no files appear**, the `when:` strings don't match the actual
|
||||
> hostnames — recheck them against `cat inventory`. That's the classic failure mode for conditionals:
|
||||
> a run that reports success while doing nothing.
|
||||
105
100 - days of devops/devops-94.md
Normal file
105
100 - days of devops/devops-94.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. This granular approach enables the team to execute the migration in gradual phases, ensuring smoother implementation and minimizing disruption to ongoing operations. By breaking down the migration into smaller tasks, the Nautilus DevOps team can systematically progress through each stage, allowing for better control, risk mitigation, and optimization of resources throughout the migration process.
|
||||
|
||||
Create a VPC named datacenter-vpc in region us-east-1 with any IPv4 CIDR block through terraform.
|
||||
|
||||
The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task.
|
||||
|
||||
Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal.
|
||||
|
||||
# Solution
|
||||
|
||||
# Terraform VPC — `datacenter-vpc`
|
||||
|
||||
Create a VPC in `us-east-1` with any IPv4 CIDR block.
|
||||
|
||||
## Create `main.tf` (heredoc → file)
|
||||
|
||||
```bash
|
||||
cd /home/bob/terraform
|
||||
|
||||
cat > main.tf <<'EOF'
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
resource "aws_vpc" "datacenter_vpc" {
|
||||
cidr_block = "10.0.0.0/16"
|
||||
|
||||
tags = {
|
||||
Name = "datacenter-vpc"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
cd /home/bob/terraform
|
||||
terraform init
|
||||
terraform apply -auto-approve
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc
|
||||
|
||||
`cat > main.tf <<'EOF'` writes the file in one shot. The **quoted** `'EOF'` disables shell expansion,
|
||||
so any `$` or backticks in HCL reach the file literally rather than being interpreted by bash — the
|
||||
right default whenever writing config files this way.
|
||||
|
||||
Note the task says **`main.tf` only** — don't split the provider block into a separate
|
||||
`provider.tf` or `versions.tf`.
|
||||
|
||||
### The provider block
|
||||
|
||||
- **`region = "us-east-1"`** — pins the deployment region as required. A VPC is a regional resource,
|
||||
so this determines where it's created.
|
||||
- **`required_providers`** with a version constraint keeps `terraform init` from pulling an
|
||||
unexpected major version.
|
||||
|
||||
### The `aws_vpc` resource
|
||||
|
||||
One resource does the whole job:
|
||||
|
||||
- **`cidr_block = "10.0.0.0/16"`** — the task allows *any* IPv4 block, so this uses a standard
|
||||
RFC 1918 private range. A `/16` gives 65,536 addresses, plenty of room to carve subnets from
|
||||
later. Any valid private CIDR (`172.16.0.0/16`, `192.168.0.0/24`, …) would satisfy the requirement
|
||||
equally; `10.0.0.0/16` is the conventional default.
|
||||
|
||||
- **`tags = { Name = "datacenter-vpc" }`** — this is what "named" means for a VPC. AWS VPCs have **no
|
||||
native name field**; the console and any grader read the `Name` tag. Omit the tag and the VPC still
|
||||
exists but shows as unnamed — the most common way to fail this task.
|
||||
|
||||
The Terraform resource label (`datacenter_vpc`) is just the internal identifier used for references
|
||||
within the config; it's unrelated to the AWS-visible name, which comes from the tag.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
aws ec2 describe-vpcs \
|
||||
--filters Name=tag:Name,Values=datacenter-vpc \
|
||||
--query 'Vpcs[0].{Id:VpcId,Cidr:CidrBlock,Name:Tags[?Key==`Name`]|[0].Value}'
|
||||
```
|
||||
|
||||
Expected — the VPC ID, `CidrBlock: 10.0.0.0/16`, and `Name: datacenter-vpc`.
|
||||
|
||||
You can also confirm from Terraform's own state:
|
||||
|
||||
```bash
|
||||
terraform state show aws_vpc.datacenter_vpc
|
||||
```
|
||||
|
||||
> If `describe-vpcs` returns nothing, the `Name` tag is missing or misspelled — the filter matches on
|
||||
> that tag, not on the resource label in the config.
|
||||
178
100 - days of devops/devops-95.md
Normal file
178
100 - days of devops/devops-95.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. This granular approach enables the team to execute the migration in gradual phases, ensuring smoother implementation and minimizing disruption to ongoing operations. By breaking down the migration into smaller tasks, the Nautilus DevOps team can systematically progress through each stage, allowing for better control, risk mitigation, and optimization of resources throughout the migration process.
|
||||
|
||||
Use Terraform to create a security group under the default VPC with the following requirements:
|
||||
|
||||
1) The name of the security group must be nautilus-sg.
|
||||
|
||||
2) The description must be Security group for Nautilus App Servers.
|
||||
|
||||
3) Add an inbound rule of type HTTP, with a port range of 80, and source CIDR range 0.0.0.0/0.
|
||||
|
||||
4) Add another inbound rule of type SSH, with a port range of 22, and source CIDR range 0.0.0.0/0.
|
||||
|
||||
Ensure that the security group is created in the us-east-1 region using Terraform. The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task.
|
||||
|
||||
Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal.
|
||||
|
||||
# Solution
|
||||
|
||||
# Terraform Security Group — `nautilus-sg`
|
||||
|
||||
Create a security group in the **default VPC** in `us-east-1` with HTTP and SSH inbound rules open
|
||||
to the world.
|
||||
|
||||
## Create `main.tf` (heredoc → file)
|
||||
|
||||
```bash
|
||||
cd /home/bob/terraform
|
||||
|
||||
cat > main.tf <<'EOF'
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
# Look up the existing default VPC
|
||||
data "aws_vpc" "default" {
|
||||
default = true
|
||||
}
|
||||
|
||||
resource "aws_security_group" "nautilus_sg" {
|
||||
name = "nautilus-sg"
|
||||
description = "Security group for Nautilus App Servers"
|
||||
vpc_id = data.aws_vpc.default.id
|
||||
|
||||
ingress {
|
||||
description = "HTTP"
|
||||
from_port = 80
|
||||
to_port = 80
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
ingress {
|
||||
description = "SSH"
|
||||
from_port = 22
|
||||
to_port = 22
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "nautilus-sg"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
cd /home/bob/terraform
|
||||
terraform init
|
||||
terraform apply -auto-approve
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc
|
||||
|
||||
`cat > main.tf <<'EOF'` writes the file in one shot. The **quoted** `'EOF'` disables shell expansion,
|
||||
so `$` and backticks in the HCL reach the file literally. The task requires everything in
|
||||
**`main.tf`** — don't split the provider block into a separate file.
|
||||
|
||||
### Finding the default VPC
|
||||
|
||||
```hcl
|
||||
data "aws_vpc" "default" {
|
||||
default = true
|
||||
}
|
||||
```
|
||||
|
||||
A **data source** *reads* an existing resource rather than creating one. Setting `default = true`
|
||||
selects the account's default VPC for the region, and `data.aws_vpc.default.id` then feeds the
|
||||
security group's `vpc_id`.
|
||||
|
||||
This is deliberately a data source, not a resource. The `aws_default_vpc` **resource** would adopt
|
||||
the default VPC into Terraform state and let Terraform modify (or on destroy, orphan) it — far more
|
||||
intrusive than needed. Reading it keeps Terraform's ownership limited to the security group itself.
|
||||
|
||||
### The security group
|
||||
|
||||
- **`name = "nautilus-sg"`** — the group name, exactly as required.
|
||||
|
||||
- **`description`** — required by AWS on every security group (Terraform defaults it to "Managed by
|
||||
Terraform" if omitted). Important: the description is **immutable** — AWS won't let you change it
|
||||
after creation. If you apply with the wrong text, you must destroy and recreate the group, so get
|
||||
it right the first time.
|
||||
|
||||
- **`vpc_id`** — pins the group to the default VPC per the requirement.
|
||||
|
||||
### The ingress rules
|
||||
|
||||
Each `ingress` block is one inbound rule:
|
||||
|
||||
| Requirement | `protocol` | `from_port` / `to_port` | `cidr_blocks` |
|
||||
|-------------|-----------|--------------------------|---------------|
|
||||
| HTTP | `tcp` | 80 / 80 | `0.0.0.0/0` |
|
||||
| SSH | `tcp` | 22 / 22 | `0.0.0.0/0` |
|
||||
|
||||
The "type" the task refers to (HTTP, SSH) is a **console-level label**, not an API field. AWS derives
|
||||
it from the protocol/port combination — `tcp` + port 80 *is* HTTP, `tcp` + port 22 *is* SSH. That's
|
||||
why there's no `type` argument in the HCL; setting the right protocol and ports is what makes the
|
||||
console display those names. The `description` field here is just a human-readable label and doesn't
|
||||
affect matching.
|
||||
|
||||
`from_port` and `to_port` define a **range**; setting both to the same value expresses a single
|
||||
port. `0.0.0.0/0` means any source IPv4 address.
|
||||
|
||||
> Security note: opening SSH (22) to `0.0.0.0/0` is fine for a lab but poor practice in production,
|
||||
> where you'd restrict it to a bastion host or known CIDR.
|
||||
|
||||
### The egress rule
|
||||
|
||||
```hcl
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
```
|
||||
|
||||
`protocol = "-1"` means **all protocols**, and with ports `0`/`0` this is the standard allow-all
|
||||
outbound rule. It's included because when Terraform manages a security group with **no** `egress`
|
||||
block, it strips AWS's default allow-all outbound rule, leaving the group unable to initiate any
|
||||
outbound traffic. The task doesn't ask about egress, so preserving the normal default is the sane
|
||||
choice.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
aws ec2 describe-security-groups \
|
||||
--filters Name=group-name,Values=nautilus-sg \
|
||||
--query 'SecurityGroups[0].{Name:GroupName,Desc:Description,Vpc:VpcId,Ingress:IpPermissions[*].{Proto:IpProtocol,From:FromPort,To:ToPort,Cidr:IpRanges[0].CidrIp}}'
|
||||
```
|
||||
|
||||
Expected — `GroupName: nautilus-sg`, the exact description string, the default VPC's ID, and two
|
||||
ingress entries: `tcp 80→80 0.0.0.0/0` and `tcp 22→22 0.0.0.0/0`.
|
||||
|
||||
> If the description is wrong, `terraform apply` **cannot** fix it in place — run
|
||||
> `terraform destroy -target=aws_security_group.nautilus_sg` and re-apply with the corrected text.
|
||||
187
100 - days of devops/devops-96.md
Normal file
187
100 - days of devops/devops-96.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units.
|
||||
|
||||
For this task, create an EC2 instance using Terraform with the following requirements:
|
||||
|
||||
The EC2 instance must use the value datacenter-ec2 as its Name tag, which defines the instance name in AWS.
|
||||
|
||||
Use the Amazon Linux ami-0c101f26f147fa7fd to launch this instance.
|
||||
|
||||
The Instance type must be t2.micro.
|
||||
|
||||
Create a new RSA key named datacenter-kp.
|
||||
|
||||
Attach the default (available by default) security group.
|
||||
|
||||
The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to provision the instance.
|
||||
|
||||
Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal.
|
||||
|
||||
# Solution
|
||||
|
||||
# Terraform EC2 Instance — `datacenter-ec2`
|
||||
|
||||
Launch a t2.micro instance with a newly-created RSA key pair and the default security group.
|
||||
|
||||
## Create `main.tf` (heredoc → file)
|
||||
|
||||
```bash
|
||||
cd /home/bob/terraform
|
||||
|
||||
cat > main.tf <<'EOF'
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 6.0"
|
||||
}
|
||||
tls = {
|
||||
source = "hashicorp/tls"
|
||||
version = "~> 4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
# --- New RSA key pair ---
|
||||
resource "tls_private_key" "datacenter_kp" {
|
||||
algorithm = "RSA"
|
||||
rsa_bits = 4096
|
||||
}
|
||||
|
||||
resource "aws_key_pair" "datacenter_kp" {
|
||||
key_name = "datacenter-kp"
|
||||
public_key = tls_private_key.datacenter_kp.public_key_openssh
|
||||
}
|
||||
|
||||
# --- Default VPC and its default security group ---
|
||||
data "aws_vpc" "default" {
|
||||
default = true
|
||||
}
|
||||
|
||||
data "aws_security_group" "default" {
|
||||
vpc_id = data.aws_vpc.default.id
|
||||
name = "default"
|
||||
}
|
||||
|
||||
# --- EC2 instance ---
|
||||
resource "aws_instance" "datacenter_ec2" {
|
||||
ami = "ami-0c101f26f147fa7fd"
|
||||
instance_type = "t2.micro"
|
||||
key_name = aws_key_pair.datacenter_kp.key_name
|
||||
vpc_security_group_ids = [data.aws_security_group.default.id]
|
||||
|
||||
credit_specification {
|
||||
cpu_credits = "standard"
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "datacenter-ec2"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
cd /home/bob/terraform
|
||||
terraform init
|
||||
terraform apply -auto-approve
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc
|
||||
|
||||
`cat > main.tf <<'EOF'` writes the file in one shot; the **quoted** `'EOF'` stops the shell from
|
||||
expanding anything in the HCL. Everything goes in **`main.tf`** as the task requires — no separate
|
||||
provider or key files.
|
||||
|
||||
### The RSA key pair (two resources)
|
||||
|
||||
AWS never hands back private key material, so creating a usable key pair takes two resources across
|
||||
two providers:
|
||||
|
||||
1. **`tls_private_key`** — generates the key pair **locally**. `algorithm = "RSA"` is what makes the
|
||||
resulting AWS key pair type `rsa`; `rsa_bits = 4096` sets its strength.
|
||||
2. **`aws_key_pair`** — uploads only the **public** half
|
||||
(`tls_private_key.datacenter_kp.public_key_openssh`) under the name `datacenter-kp`. AWS stores
|
||||
just the public key; because it's RSA, the key pair registers with `KeyType: rsa`.
|
||||
|
||||
Referencing the TLS resource's attribute creates an **implicit dependency**, so Terraform generates
|
||||
the key before trying to import it.
|
||||
|
||||
> The task doesn't ask for the private key to be saved to disk, so no `local_file` resource is
|
||||
> included. It lives in Terraform state only. If you later need to SSH in, add a
|
||||
> `local_sensitive_file` writing `tls_private_key.datacenter_kp.private_key_pem` with `0400`
|
||||
> permissions.
|
||||
|
||||
### The default security group
|
||||
|
||||
```hcl
|
||||
data "aws_vpc" "default" { default = true }
|
||||
|
||||
data "aws_security_group" "default" {
|
||||
vpc_id = data.aws_vpc.default.id
|
||||
name = "default"
|
||||
}
|
||||
```
|
||||
|
||||
Two **data sources** read existing infrastructure rather than creating it: the account's default VPC,
|
||||
then the security group named `default` **within that VPC**. Scoping by `vpc_id` matters — every VPC
|
||||
has its own group named "default", so the name alone is ambiguous.
|
||||
|
||||
The group is then attached via **`vpc_security_group_ids`**, which takes a list of security group
|
||||
**IDs**. (The older `security_groups` argument takes names and is for EC2-Classic; on modern VPC
|
||||
instances `vpc_security_group_ids` is correct.)
|
||||
|
||||
Using data sources keeps Terraform from taking ownership of the default SG — it only references it.
|
||||
|
||||
### The instance
|
||||
|
||||
- **`ami = "ami-0c101f26f147fa7fd"`** — hardcoded exactly as given, so no AMI lookup data source is
|
||||
needed.
|
||||
- **`instance_type = "t2.micro"`** — as required.
|
||||
- **`key_name = aws_key_pair.datacenter_kp.key_name`** — attaches the key pair by reference,
|
||||
producing an implicit dependency so the key exists before the instance launches.
|
||||
- **`tags = { Name = "datacenter-ec2" }`** — an EC2 instance's displayed name comes from the `Name`
|
||||
**tag**, not a native field. Omit it and the instance runs but appears unnamed — the usual way to
|
||||
fail this requirement. The Terraform resource label (`datacenter_ec2`) is unrelated; it's only an
|
||||
internal reference.
|
||||
|
||||
### Why `credit_specification` is pinned
|
||||
|
||||
```hcl
|
||||
credit_specification {
|
||||
cpu_credits = "standard"
|
||||
}
|
||||
```
|
||||
|
||||
Burstable T-family instances run in either **standard** or **unlimited** CPU-credit mode. In
|
||||
`unlimited`, an instance can burn credits beyond its baseline and incur surcharge billing — which
|
||||
constrained sandbox environments actively police, sometimes by resetting the instance or suspending
|
||||
the session. `t2.micro` defaults to `standard`, so this is belt-and-braces, but pinning it removes
|
||||
any chance of the mode drifting to `unlimited`.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
aws ec2 describe-instances \
|
||||
--filters Name=tag:Name,Values=datacenter-ec2 Name=instance-state-name,Values=pending,running \
|
||||
--query 'Reservations[0].Instances[0].{Id:InstanceId,Type:InstanceType,Key:KeyName,SG:SecurityGroups[0].GroupName,State:State.Name}'
|
||||
|
||||
aws ec2 describe-key-pairs --key-names datacenter-kp \
|
||||
--query 'KeyPairs[0].{Name:KeyName,Type:KeyType}'
|
||||
```
|
||||
|
||||
Expected — the instance showing `InstanceType: t2.micro`, `KeyName: datacenter-kp`, security group
|
||||
`default`, and state `pending` then `running` (it takes a minute or two); and the key pair reporting
|
||||
`KeyType: rsa`.
|
||||
|
||||
> If `describe-instances` returns nothing, check the `Name` tag — the filter matches on that tag, not
|
||||
> the Terraform resource label.
|
||||
Reference in New Issue
Block a user