কম্পিউটার টিউটোরিয়াল

ব্যাশ অটোমেশন দিয়ে কুবারনেটিস হেল্ম ডিপ্লয়মেন্ট স্ট্রিমলাইন করুন

আমাদের কিছু অ্যাপ্লিকেশন একটি কুবারনেটিস ক্লাস্টারে হোস্ট করা থাকে, এবং আমরা GitLab Continuous Integration (CI) ব্যবহার করি ডিপ্লয়মেন্ট অটোমেট করতে এবং আমাদের অ্যাপ্লিকেশন ডিপ্লয় করতে Helm 2 ব্যবহার করি। Helm চার্টস Kubernetes অবজেক্ট YAML ফাইল টেমপ্লেট স্টোর করতে সক্ষম করে ভেরিয়েবলসহ যেগুলো প্রোগ্রাম্যাটিক্যালি সেট করা যায় কমান্ড-লাইন আর্গুমেন্ট থেকে যা চার্ট ব্যবহার করার সময় ডিপ্লয়মেন্টের время পাস করা হয়। এটি আমাদের ক্রিটিক্যাল সিক্রেটস GitLab-প্রোটেক্টেড এনভায়রনমেন্ট ভেরিয়েবলে বা Hashicorp Vault-এ স্টোর করতে এবং সেগুলো CI ডিপ্লয়মেন্ট জবের মধ্যে ব্যবহার করতে দেয়।

আমাদের ডিপ্লয়মেন্ট জব একটি ব্যাশ স্ক্রিপ্ট ব্যবহার করে ডিপ্লয়মেন্ট প্রসেস রান করতে। এই ব্যাশ স্ক্রিপ্টটি CI/CD এনভায়রনমেন্টে ব্যবহারের জন্য অনেক ценный ফিচার প্রেজেন্ট করে:

  1. CI/CD এনভায়রনমেন্টের বাইরে ব্যবহার সোশাল করুন। GitLab CI এবং অন্যান্য CI সিস্টেম জব স্টেপস স্টোর করে এক্সিকিউটেবল শেল কোডের লাইন হিসেবে একটি "script" সেকশনে একটি CI টেক্সট ফাইলে (.gitlab-ci.yml, উদাহরণস্বরূপ)। যদিও এটি বেসিক এক্সিকিউটেবল স্টেপস স্টোর করার জন্য উপযোগী এক্সটার্নাল ডিপেন্ডেন্সি ছাড়া, এটি ডেভেলপারদের একই কোড টেস্টিং বা ম্যানুয়াল ডিপ্লয়মেন্ট সিনেরিওতে ব্যবহার করতে বাধা দেয়। এছাড়া, Bash সিস্টেমের অনেক অ্যাডভান্সড ফিচার এই স্ক্রিপ্ট সেকশনে সহজে ব্যবহার করা যায় না।
  2. মহৎ ডিপ্লয়মেন্ট প্রসেসের ইউনিট টেস্টিং সোশাল করুন। কোনো CI সিস্টেম ডিপ্লয়মেন্ট লজিক প্রত্যাশা অনুযায়ী পারফর্ম করছে কিনা টেস্ট করার উপায় দেয় না। যত্নপূর্বক কনস্ট্রাক্টেড ব্যাশ স্ক্রিপ্টগুলো BATS দিয়ে ইউনিট টেস্ট করা যায়।
  3. স্ক্রিপ্টের মধ্যে indywidual ফাংশন রিইউজ সোশাল করুন। লাস্ট সেকশন একটি গার্ড ক্লজ ব্যবহার করে, if [[ "${BASH_SOURCE[0]}" == "${0}" ]], যা run_main ফাংশনকে কল হয়ে যাওয়া থেকে রোধ করে যখন স্ক্রিপ্ট এক্সিকিউট হচ্ছে না। এটি স্ক্রিপ্টকে সোর্স করার অনুমতি দেয়, যা যেইভাবে ব্যবহারকারীদের স্ক্রিপ্টের মধ্যে অনেক উপকারী indywidual ফাংশন ব্যবহার করতে দেয়। এটি প্রপার BATS টেস্টিং-এর জন্য জরুরি।
  4. এনভায়রনমেন্ট ভেরিয়েবল ব্যবহার করে সেনসিটিভ ইনফরমেশন প্রোটেক্ট করুন এবং স্ক্রিপ্টটিকে অনেক প্রজেক্ট এবং প্রজেক্ট অ্যাপ্লিকেশন এনভায়রনমেন্টে রিইউজযোগ্য বানান। GitLab CI এই এনভায়রনমেন্ট ভেরিয়েবলগুলো উপলব্ধ করায় যখন GitLab CI রানার দ্বারা রান করা হয়। GitLab CI-র বাইরে স্ক্রিপ্ট ব্যবহার করার আগে এগুলো ম্যানুয়ালি সেট করা হবে।

স্ক্রিপ্টটি একটি অ্যাপ্লিকেশনের জন্য Kubernetes-এ একটি Helm চার্ট ডিপ্লয় করার জন্য প্রয়োজনীয় সকল টাস্ক পারফর্ম করে এবং ডিপ্লয়মেন্ট রেডি হওয়ার জন্য kubectl এবং Helm ব্যবহার করে অপেক্ষা করে। Helm একটি লোকাল Tiller ইনস্টলেশন দিয়ে চলে রান করে Tiller কুবারনেটিস ক্লাস্টারে না চালিয়ে। Kubernetes HELM_USER এবং HELM_PASSWORD ব্যবহার করা হয় কুবারনেটিস CLUSTER_SERVER এবং PROJECT_NAMESPACE-এ লগইন করতে। Tiller শুরু হয়, Helm ক্লায়েন্ট-ওনলি মোডে ইনিশিয়ালাইজ হয়, এবং এর রেপো আপডেট হয়। টেমপ্লেটটি Helm দিয়ে লিন্ট করা হয় যেন সিনট্যাক্স এরর অকস্মাত কমিট হয়নি। টেমপ্লেটটি ডিক্লেরেটিভ মোডে ডিপ্লয় করা হয়, helm upgrade --install ব্যবহার করে। Helm ডিপ্লয়মেন্ট রেডি হওয়ার জন্য অপেক্ষা করে --wait flag ব্যবহার করে।

স্ক্রিপ্টটি নিশ্চিত করে যে নির্দিষ্ট টেমপ্লেট ভেরিয়েবল ডিপ্লয়মেন্টের সময় সেট আছে এবং 스페শাল প্রজেক্ট-স্পেসিফিক ভেরিয়েবল GitLab CI PROJECT_SPECIFIC_DEPLOY_ARGS এনভায়রনমেন্ট ভেরিয়েবলে স্পেসিফাই করা যেতে পারে। ডিপ্লয়মেন্টে প্রয়োজনীয় সকল এনভায়রনমেন্ট ভেরিয়েবল স্ক্রিপ্ট এক্সিকিউশনের আর্লি স্টেজে চেক করা হয়, এবং স্ক্রিপ্ট নন-জিরো এক্সিট স্ট্যাটাস দিয়ে এক্সিট করে যদি কোনো মিসিং থাকে।

এই স্ক্রিপ্টটি একাধিক GitLab CI-হোস্টেড প্রজেক্টে ব্যবহার করা হয়েছে। এটি আমাদের প্রতিটি প্রজেক্টে ডিপ্লয়মেন্ট লজিকের চেয়ে আমাদের কোডের ওপর ফোকাস করতে সাহায্য করেছে।

স্ক্রিপ্ট

#!/bin/bash

# MIT License
#
# Copyright (c) 2019 Darin London
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

log_level_for()
{
case "${1}" in
"error")
echo 1
;;

"warn")
echo 2
;;

"debug")
echo 3
;;

"info")
echo 4
;;
*)
echo -1
;;
esac
}

current_log_level()
{
log_level_for "${LOG_LEVEL}"
}

error()
{
[ $(log_level_for "error") -le $(current_log_level) ] && echo "${1}" >&2
}

warn()
{
[ $(log_level_for "warn") -le $(current_log_level) ] && echo "${1}" >&2
}

debug()
{
[ $(log_level_for "debug") -le $(current_log_level) ] && echo "${1}" >&2
}

info()
{
[ $(log_level_for "info") -le $(current_log_level) ] && echo "${1}" >&2
}

check_required_environment() {
local required_env="${1}"

for reqvar in $required_env
do
if [ -z "${!reqvar}" ]
then
error "missing ENVIRONMENT ${reqvar}!"
return 1
fi
done
}

check_default_environment() {
local required_env="${1}"

for varpair in $required_env
do
local manual_environment=$(echo "${varpair}" | cut -d':' -f1)
local default_if_not_set=$(echo "${varpair}" | cut -d':' -f2)
if [ -z "${!manual_environment}" ] && [ -z "${!default_if_not_set}" ]
then
error "missing default ENVIRONMENT, set ${manual_environment} or ${default_if_not_set}!"
return 1
fi
done
}

dry_run() {
[ ${DRY_RUN} ] && info "skipping for dry run" && return
return 1
}

init_tiller() {
info "initializing local tiller"
dry_run && return

export TILLER_NAMESPACE=$PROJECT_NAMESPACE
export HELM_HOST=localhost:44134
# https://rimusz.net/tillerless-helm/
# run tiller locally instead of in the cluster
tiller --storage=secret &
export TILLER_PID=$!
sleep 1
kill -0 ${TILLER_PID}
if [ $? -gt 0 ]
then
error "tiller not running!"
return 1
fi
}

init_helm() {
info "initializing helm"
dry_run && return

helm init --client-only
if [ $? -gt 0 ]
then
error "could not initialize helm"
return 1
fi
}

init_helm_with_tiller() {
init_tiller || return 1
init_helm || return 1
info "updating helm client repository information"
dry_run && return
helm repo update
if [ $? -gt 0 ]
then
error "could not update helm repository information"
return 1
fi
}

decommission_tiller() {
if [ -n "${TILLER_PID}" ]
then
kill ${TILLER_PID}
if [ $? -gt 0 ]
.then
return
fi
fi
}

check_required_deploy_arg_environment() {
[ -z "${PROJECT_SPECIFIC_DEPLOY_ARGS}" ] && return
for reqvar in ${PROJECT_SPECIFIC_DEPLOY_ARGS}
do
if [ -z ${!reqvar} ]
then
error "missing Deployment ENVIRONMENT ${reqvar} required!"
return 1
fi
done
}

project_specific_deploy_args() {
[ -z "${PROJECT_SPECIFIC_DEPLOY_ARGS}" ] && echo "" && return

extraArgs=''
for deploy_arg_key in ${PROJECT_SPECIFIC_DEPLOY_ARGS}
do
extraArgs="${extraArgs} --set $(echo "${deploy_arg_key}" | sed 's/__/\./g' | tr '[:upper:]' '[:lower:]')=${!deploy_arg_key}"
done

echo "${extraArgs}"
}

check_required_cluster_login_environment() {
check_required_environment "HELM_TOKEN HELM_USER PROJECT_NAMESPACE CLUSTER_SERVER" || return 1
}

cluster_login() {
info "authenticating ${HELM_USER} in ${PROJECT_NAMESPACE}"
dry_run && return

kubectl config set-cluster ci_kube --server="${CLUSTER_SERVER}" || return 1
kubectl config set-credentials "${HELM_USER}" --token="${HELM_TOKEN}" || return 1
kubectl config set-context ${PROJECT_NAMESPACE}-deploy --cluster=ci_kube --namespace=${PROJECT_NAMESPACE} --user=${HELM_USER} || return 1
kubectl config use-context ${PROJECT_NAMESPACE}-deploy || return 1
}

lint_template() {
info "linting template"
dry_run && return

helm lint ${CI_PROJECT_DIR}/helm-chart/${CI_PROJECT_NAME}
}

check_required_image_pull_environment() {
if [ "${CI_PROJECT_VISIBILITY}" == "public" ]
then
check_required_environment "CI_REGISTRY CI_DEPLOY_USER CI_DEPLOY_PASSWORD" || return 1
fi
}

image_pull_settings() {
if [ "${CI_PROJECT_VISIBILITY}" == "public" ]
then
echo ""
else
echo "--set registry.root=${CI_REGISTRY} --set registry.secret.username=${CI_DEPLOY_USER} --set registry.secret.password=${CI_DEPLOY_PASSWORD}"
fi
}

deployment_name() {
if [ -n "${DEPLOYMENT_NAME}" ]
then
echo "${DEPLOYMENT_NAME}"
else
echo "${CI_ENVIRONMENT_SLUG}-${CI_PROJECT_NAME}"
fi
}

deploy_template() {
info "deploying $(deployment_name) from template"
if dry_run
then
info "helm upgrade --force --recreate-pods --debug --set image.repository=${CI_REGISTRY_IMAGE}/${CI_PROJECT_NAME} --set image.tag=${CI_COMMIT_SHORT_SHA} --set environment=${CI_ENVIRONMENT_NAME} --set-string git_commit=${CI_COMMIT_SHORT_SHA} --set git_ref=${CI_COMMIT_REF_SLUG} --set ci_job_id=${CI_JOB_ID} $(environment_url_settings) $(image_pull_settings) $(project_specific_deploy_args) --wait --install $(deployment_name) ${CI_PROJECT_DIR}/helm-chart/${CI_PROJECT_NAME}"
else
helm upgrade --force --recreate-pods --debug \
--set image.repository="${CI_REGISTRY_IMAGE}/${CI_PROJECT_NAME}" \
--set image.tag="${CI_COMMIT_SHORT_SHA}" \
--set environment="${CI_ENVIRONMENT_NAME}" \
--set-string git_commit="${CI_COMMIT_SHORT_SHA}" \
--set git_ref="${CI_COMMIT_REF_SLUG}" \
--set ci_job_id="${CI_JOB_ID}" \
$(image_pull_settings) \
$(project_specific_deploy_args) \
--wait \
--install $(deployment_name) ${CI_PROJECT_DIR}/helm-chart/${CI_PROJECT_NAME}
fi
}

get_pods() {
kubectl get pods -l ci_job_id="${CI_JOB_ID}"
}

watch_deployment() {
local watch_deployment=$(deployment_name)
if [ -n "${WATCH_DEPLOYMENT}" ]
then
watch_deployment="${WATCH_DEPLOYMENT}"
fi
info "waiting until deployment ${watch_deployment} is ready"
dry_run && return

kubectl rollout status deployment/${watch_deployment} -w || return 1
sleep 5
get_pods || return 1
# see what has been deployed
kubectl describe deployment -l app=${CI_PROJECT_NAME},environment=${CI_ENVIRONMENT_NAME},git_commit=${CI_COMMIT_SHORT_SHA} || return 1
if [ -n "${CI_ENVIRONMENT_URL}" ]
then
kubectl describe service -l app=${CI_PROJECT_NAME},environment=${CI_ENVIRONMENT_NAME} || return 1
kubectl describe route -l app=${CI_PROJECT_NAME},environment=${CI_ENVIRONMENT_NAME} || return 1
fi
}

run_main() {
check_required_environment "CI_PROJECT_NAME CI_PROJECT_DIR CI_COMMIT_REF_SLUG CI_REGISTRY_IMAGE CI_ENVIRONMENT_NAME CI_JOB_ID CI_COMMIT_SHORT_SHA" || return 1
check_default_environment "WATCH_DEPLOYMENT:CI_ENVIRONMENT_SLUG" || return 1
check_required_deploy_arg_environment || return 1
check_required_cluster_login_environment || return 1
check_required_image_pull_environment || return 1
cluster_login
if [ $? -gt 0 ]
then
error "could not login kubectl"
return 1
fi

init_helm_with_tiller
if [ $? -gt 0 ]
then
error "could not initialize helm"
return 1
fi

lint_template
if [ $? -gt 0 ]
then
error "linting failed"
return 1
fi

deploy_template
if [ $? -gt 0 ]
then
error "could not deploy template"
return 1
fi

watch_deployment
if [ $? -gt 0 ]
then
error "could not watch deployment"
return 1
fi

decommission_tiller
info "ALL Complete!"
return
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]
then
run_main
if [ $? -gt 0 ]
then
exit 1
fi
fi

  1. সিকিউরিটি স্ট্রিমলাইন: ACLSync দিয়ে অ্যাক্সেস কন্ট্রোল অটোমেট করুন

  2. Bash অটোমেশন স্ক্রিপ্ট দিয়ে ইমেজ অপ্টিমাইজেশন স্ট্রিমলাইন করুন

  3. সহজে ঘরে ডাবল-সাইডেড ডকুমেন্ট প্রিন্ট করুন একটি সাধারণ ব্যাশ স্ক্রিপ্ট দিয়ে

  4. ব্যাশ স্ক্রিপ্টিং মাস্টার করুন: ৫টি রিয়েল-ওয়ার্ল্ড উদাহরণসহ প্র্যাক্টিক্যাল ইন্ট্রোডাকশন