使用Kind: deployment限制pod在kubernetes中的首次部署



我有几个pod,其中需要逐个执行rollout(初始和后续更新)。(实际上只有第一个需要准备好,剩下的才能启动或升级)我使用有状态集,因为它确保一次只更新或创建一个,但我们使用远程呈现进行开发,它不支持替换有状态集。因此,我认为我可以使用部署而不是滚动更新策略的状态集,并限制maxunavailable或maxsurge或其他任何东西的数量来"限制"部署。但是对于初始部署来说,这不起作用,因为k8会一个接一个地创建所需的2个。是否有一种方法可以实现部署,或者我需要使用有状态集?(或者:是否有一个技巧来使用远程呈现与有状态集)

根据评论中的问题进行澄清:

  • 这里的问题软件是flyway与mariadb在集群模式下的组合。然后表锁定不起作用,同时启动的pod可以尝试同时执行模式和数据更新
  • init容器没有帮助,因为它们对pod的多个实例同时启动,并且只需确保每个实例的主容器在init容器
  • 之后启动即可。
  • 问题只在第一次初始化,因为之后我可以配置滚动更新策略,一次只更新一个容器。在扩展的情况下,我必须以1为增量来做,但这将是一个手动过程。
  • 我可以确保新部署的部署描述符使用规模1,然后更新到规模2,但这会导致一个非常复杂的自动部署过程,依赖于状态的可变规模,构建链需要检查部署是否存在,以确定它是更新还是第一次部署。这将是容易出错和过于复杂的

在我看来,有两种可能的解决方案,但都需要额外的努力。
我将介绍两种解决方案,你可以选择最适合你的。


解决方案一:使用脚本部署应用程序

您可以使用下面的脚本部署您的应用程序:

$ cat deploy.sh
#!/bin/bash
# Usage: deploy.sh DEPLOYMENT_FILENAME NAMESPACE NUMBER_OF_REPLICAS
deploymentFileName=$1   # Deployment manifest file name
namespace=$2            # Namespace where app should be deployed
replicas=$3             # Numbers of replicas that should be deployed
# First deploy ONLY one replica - sed command changes actual number of replicas to 1 but the original manifest file remains intact
cat ${deploymentFileName} | sed -E 's/replicas: [0-9]+$/replicas: 1/' | kubectl apply -n $namespace -f -
# The "until" loop waits until the first replica is ready
# Check deployment rollout status every 10 seconds (max 10 minutes) until complete.
attempts=0
rollout_cmd="kubectl rollout status -f ${deploymentFileName} -n $namespace"
until $rollout_cmd || [ $attempts -eq 60 ]; do
$rollout_cmd
attempts=$((attempts + 1))
sleep 10
done
if [ $attempts -eq 60 ]; then
echo "ERROR: Timeout"
exit 1
fi
# With the first replica running, deploy the rest unless we want to deploy only one replica
if [ $replicas -ne 1 ]; then
kubectl scale -f ${deploymentFileName} -n $namespace --replicas=${replicas}
fi

我创建了一个简单的例子来说明它是如何工作的。

首先,我创建了web-app.yml部署清单文件:
$ kubectl create deployment web-app --image=nginx --replicas=3 --dry-run=client -oyaml > web-app.yml

然后我使用deploy.sh脚本部署web-app部署:

$ ./deploy.sh web-app.yml default 3
deployment.apps/web-app created
Waiting for deployment "web-app" rollout to finish: 0 of 1 updated replicas are available...
deployment "web-app" successfully rolled out
deployment.apps/web-app scaled

在另一个终端窗口中,我们可以看到,只有当第一个副本(web-app-5cd54cb75-krgtc)处于"运行"状态时,状态,休息开始了:

$ kubectl get pod -w
NAME                      READY   STATUS             RESTARTS   AGE
web-app-5cd54cb75-krgtc   0/1     Pending             0          0s
web-app-5cd54cb75-krgtc   0/1     Pending             0          0s
web-app-5cd54cb75-krgtc   0/1     ContainerCreating   0          0s
web-app-5cd54cb75-krgtc   1/1     Running             0          4s # First replica in the "Running state"
web-app-5cd54cb75-tmpcn   0/1     Pending             0          0s
web-app-5cd54cb75-tmpcn   0/1     Pending             0          0s
web-app-5cd54cb75-sstg6   0/1     Pending             0          0s
web-app-5cd54cb75-tmpcn   0/1     ContainerCreating   0          0s
web-app-5cd54cb75-sstg6   0/1     Pending             0          0s
web-app-5cd54cb75-sstg6   0/1     ContainerCreating   0          0s
web-app-5cd54cb75-tmpcn   1/1     Running             0          5s
web-app-5cd54cb75-sstg6   1/1     Running             0          7s

方案二:使用initContainers

你可以使用init Container,它会运行一个脚本来决定哪个Pod应该首先运行:

$ cat checker.sh
#!/bin/bash
labelName="app" # label key of you rapplication
labelValue="web" # label value of your application
hostname=$(hostname) 
apt update && apt install -y jq 1>/dev/null 2>&1 # install the jq program - command-line JSON processor
startFirst=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/?labelSelector=${labelName}%3D${labelValue}&limit=500" | jq '.items[].metadata.name' | sort | head -n1 | tr -d '""') # determine which Pod should start first -> kubectl get pods -l app=web -o=name | sort | head -n1
firstPodStatusChecker=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/${startFirst}"| jq '.status.phase' | tr -d '""') # check status of the Pod that should start first
attempts=0
if [ ${hostname} != ${startFirst} ]
then
while [ ${firstPodStatusChecker} != "Running" ] && [ $attempts -lt 60 ]; do
attempts=$((attempts + 1))
sleep 5
firstPodStatusChecker=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/${startFirst}"| jq '.status.phase' | tr -d '""') # check status of the Pod that should start first
done
fi
if [ $attempts -eq 60 ]; then
echo "ERROR: Timeout"
exit 1
fi

这个脚本中最重要的一行是:

startFirst=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/?labelSelector=${labelName}%3D${labelValue}&limit=500" | jq '.items[].metadata.name' | sort | head -n1 | tr -d '""')

这一行决定哪个Pod应该首先启动,其余的副本将等待,直到第一个Pod启动。我使用curl命令从Pod访问API。我们不需要手动创建复杂的curl命令,但我们可以很容易地转换kubectl命令到curl命令-如果你运行kubectl命令和-v=10选项,你可以看到curl请求。

注意:在这种方法中,您需要为ServiceAccount添加与API通信的适当权限。例如,您可以向ServiceAccount添加一个view角色,如下所示:

$ kubectl create clusterrolebinding --serviceaccount=default:default --clusterrole=view default-sa-view-access
clusterrolebinding.rbac.authorization.k8s.io/default-sa-view-access created

您可以挂载checker.sh脚本,例如作为ConfigMap:

$ cat check-script-configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: check-script
data:
checkScript.sh: |
#!/bin/bash
labelName="app" # label key of you rapplication
labelValue="web" # label value of your application
hostname=$(hostname) 
apt update && apt install -y jq 1>/dev/null 2>&1 # install the jq program - command-line JSON processor
startFirst=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/?labelSelector=${labelName}%3D${labelValue}&limit=500" | jq '.items[].metadata.name' | sort | head -n1 | tr -d '""') # determine which Pod should start first -> kubectl get pods -l app=web -o=name | sort | head -n1
firstPodStatusChecker=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/${startFirst}"| jq '.status.phase' | tr -d '""') # check status of the Pod that should start first
attempts=0  
if [ ${hostname} != ${startFirst} ]
then
while [ ${firstPodStatusChecker} != "Running" ] && [ $attempts -lt 60 ]; do
attempts=$((attempts + 1))
sleep 5
firstPodStatusChecker=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/${startFirst}"| jq '.status.phase' | tr -d '""') # check status of the Pod that should start first
done
fi
if [ $attempts -eq 60 ]; then
echo "ERROR: Timeout"
exit 1
fi

我还创建了一个简单的例子来说明它是如何工作的。

首先,我创建了上面的check-scriptConfigMap:
$ kubectl apply -f check-script-configmap.yml
configmap/check-script created

然后我将这个ConfigMap安装到initContainer上,并部署了这个部署:

$ cat web.yml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
volumes:
- name: check-script
configMap:
name: check-script
initContainers:
- image: nginx
name: init
command: ["bash", "/mnt/checkScript.sh"]
volumeMounts:
- name: check-script
mountPath: /mnt/
containers:
- image: nginx
name: nginx
$ kubectl apply -f web.yml
deployment.apps/web created

在另一个终端窗口中,我们可以看到,只有当第一个副本(web-98c4d45dd-6zcsd)处于"Running"状态,休息开始了:

$ kubectl get pod -w
NAME                  READY   STATUS    RESTARTS   AGE
web-98c4d45dd-ztjlf   0/1     Pending   0          0s
web-98c4d45dd-ztjlf   0/1     Pending   0          0s
web-98c4d45dd-6zcsd   0/1     Pending   0          0s
web-98c4d45dd-mc2ww   0/1     Pending   0          0s
web-98c4d45dd-6zcsd   0/1     Pending   0          0s
web-98c4d45dd-mc2ww   0/1     Pending   0          0s
web-98c4d45dd-ztjlf   0/1     Init:0/1   0          0s
web-98c4d45dd-6zcsd   0/1     Init:0/1   0          0s
web-98c4d45dd-mc2ww   0/1     Init:0/1   0          1s
web-98c4d45dd-6zcsd   0/1     Init:0/1   0          3s
web-98c4d45dd-ztjlf   0/1     Init:0/1   0          3s
web-98c4d45dd-mc2ww   0/1     Init:0/1   0          4s
web-98c4d45dd-6zcsd   0/1     PodInitializing   0          10s
web-98c4d45dd-6zcsd   1/1     Running           0          12s
web-98c4d45dd-mc2ww   0/1     PodInitializing   0          23s
web-98c4d45dd-ztjlf   0/1     PodInitializing   0          23s
web-98c4d45dd-mc2ww   1/1     Running           0          25s
web-98c4d45dd-ztjlf   1/1     Running           0          26s

最新更新