kubernetes的官方文档:https://kubernetes.io/zh/docs/concepts/overview/what-is-kubernetes/
kubernetes + cncf其他软件 = 云原生
一、K8s的基础架构原理
1、K8s使用的时master-node架构
master与worker如何交互?
master决定worker里面有什么?
worker只是和master(api)通讯;每个节点自己干自己的活;
程序员使用UI或者CLI操作K8s集群的master节点,就可以知道整个集群的状况,因为都在master这边进行管理!
2、K8s的相关组件
官方文档:https://kubernetes.io/zh/docs/concepts/overview/components/
Control Plane(Master节点):控制面板,控制着整个集群;Master节点上的核心组件:
c-m(Controller Manager):控制器管理器(管理着很多控制器,具体做任务的是这个真正的控制器)
控制器的作用是:不断地以受控的速率,改变实际状态,使其最终变为期望值状态!
每一种类型的工作负载有自己的控制器,所有的控制器:https://kubernetes.io/zh/docs/concepts/workloads/controllers/
Deployment:部署控制器,整体控制每次的部署,远比ReplicaSet控制器强大,可以实现滚动升级,灰度发布,版本回退等!
ReplicaSet:控制副本
StatefulSets:
DaemonSet
Jobs
etcd:键值数据库(就像redis)
scheduler:调度器
api server:api网关
Node(Worker节点):工作节点;Worker节点上的核心组件:
kubelet(监工):每一个node节点上必须安装的组件,随时和Master节点保持联系,看看有没有自己的活要干;以及当前Node节点上应用容器的启停!
在Worker节点:监工,指挥别人干活;
在Master节点:master的小助手,活不用master干,而是由小助手帮忙干!
kube-proxy:代理,代理网络
Pod应用:由上面kubelet启动的容器成为Pod,Pod是K8s的基本单位!
Pod是对Docker容器的再封装,Docker的修改会被K8s的Pod屏蔽掉;
一个容器往往代表不了一个基本应用,而一个Pod里面可以包含多个容器,从而构成一个完整的应用;——相当于docker-compose启动的基本应用!
container是Docker里的基本单位,Pod是K8s里的基本单位,Pod >= container
程序员部署一个应用的流程:
通过UI或者调用CLI调用Master的API Server网关,我们现在要部署一个tomcat应用,API Server网关是Master节点的唯一入口;
API Server收到请求后,会把请求交给Controller Manager进行控制处理;
Controller Manager会生成依次部署信息,并将部署信息保存到etcd键值数据库中;
scheduler调度器从etcd数据库中,获取到要部署的应用任务,开始调度,分析哪个Node节点合适完成此次部署任务,它会将算出来的调度信息,再次放入到etcd数据库中;
每一个Node节点的kubelet(监工),随时和Master的API Server保持联系,当有自己节点的任务需要处理的时候,Kubelet就自己根据任务run一个容器,并随时给Master节点汇报当前应用的状态信息;
Kubelet在自己的机器上run了一个容器后,但是这个容器的网络ip等信息如何确定,这块也是比较复杂的,所以就专门设计了k-proxy网络代理,专门处理Node上容器的网络信息,以及向内向外的数据流量转发;
综上,无论是UI界面,或者CLI客户端,还是Worker Node节点,与Master节点的通讯都是通过API Server网关完成了,即Master节点的唯一入口!
甚至,即使同为Master节点中的组件,相互之间的通讯,也得经过API Server!
3、Master-Worker节点的基于list-watch机制的控制器架构
想让K8s部署一个tomcat:
0. 开机默认所有节点的kubelet、master节点的scheduler(调度器)、controller-manager(控制管理器)一直监听master节点的api-server发来的事件变化(for::循环);
程序员使用命令行工具:kubelet create deploy tomcat –image=tomcat8 (告诉master节点让集群使用tomcat8镜像,部署一个tomcat应用);
kubectl命令会将命令内容发给api-server,api-server保存此次创建信息到etcd数据库【deploy部署信息】;
etcd数据库给api-server上报事件(刚才有人给我里面保存了信息)【deploy部署信息】;
controller-manager专门从api-server中监听【deploy部署信息】;
controller-manager处理这个事件(部署tomcat[deploy]),生成Pod的部署信息【Pod信息】;
controller-manager将生成的Pod的部署信息交给api-server,由api-server保存到etcd数据库;
etcd数据库再次上报事件【Pod信息】给api-server;
scheduler调度器专门从api-server中监听【Pod信息】,拿到【Pod信息】后,进行计算,看看哪个Node节点适合部署此次Pod部署任务;【pod信息+node号】
scheduler调度器将生成好的【pod信息+node号】交给api-server,由api-server再次保存到etcd数据库;
etcd数据库再次上报事件【pod信息+node号】给api-server;
所有节点的kubelet专门从api-server中监听【pod信息+node号】,拿到【pod信息+node号】后,判断是否属于自己的任务,并处理;
二、K8s的安装前置准备工作
我本机电脑资源优先,就1Master+1Node吧!
0、K8s的安装有3中常见方式:
-
-
MiniKube…..
-
大致流程:
-
-
安装Docker容器化环境【k8s放弃dockershim】
-
安装Kubernetes
-
三台机器安装核心组件(kubeadm(创建集群的引导工具), kubelet,kubectl(程序员用的命令行) )
-
kubelet可以直接通过容器化的方式创建出之前的核心组件(api-server)【官方把核心组件做成镜像】
-
1、配置机器的基础环节(hostname&网络&swap分区等)
######################################################################### #关闭防火墙: 如果是云服务器,需要设置安全组策略放行端口 # https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#check-required-ports systemctl stop firewalld systemctl disable firewalld # 修改 hostname hostnamectl set-hostname k8s-01 # 查看修改结果 hostnamectl status # 设置 hostname 解析 echo "127.0.0.1 $(hostname)" >> /etc/hosts #关闭 selinux: sed -i 's/enforcing/disabled/' /etc/selinux/config setenforce 0 #关闭 swap: swapoff -a sed -ri 's/.*swap.*/#&/' /etc/fstab #通过free -m确保swap分区都为0即完成 #允许 iptables 检查桥接流量 #https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#%E5%85%81%E8%AE%B8-iptables-%E6%A3%80%E6%9F%A5%E6%A1%A5%E6%8E%A5%E6%B5%81%E9%87%8F ## 开启br_netfilter ## sudo modprobe br_netfilter ## 确认下 ## lsmod | grep br_netfilter ## 修改配置 #####这里用这个,不要用课堂上的配置。。。。。。。。。 #将桥接的 IPv4 流量传递到 iptables 的链: # 修改 /etc/sysctl.conf # 如果有配置,则修改 sed -i "s#^net.ipv4.ip_forward.*#net.ipv4.ip_forward=1#g" /etc/sysctl.conf sed -i "s#^net.bridge.bridge-nf-call-ip6tables.*#net.bridge.bridge-nf-call-ip6tables=1#g" /etc/sysctl.conf sed -i "s#^net.bridge.bridge-nf-call-iptables.*#net.bridge.bridge-nf-call-iptables=1#g" /etc/sysctl.conf sed -i "s#^net.ipv6.conf.all.disable_ipv6.*#net.ipv6.conf.all.disable_ipv6=1#g" /etc/sysctl.conf sed -i "s#^net.ipv6.conf.default.disable_ipv6.*#net.ipv6.conf.default.disable_ipv6=1#g" /etc/sysctl.conf sed -i "s#^net.ipv6.conf.lo.disable_ipv6.*#net.ipv6.conf.lo.disable_ipv6=1#g" /etc/sysctl.conf sed -i "s#^net.ipv6.conf.all.forwarding.*#net.ipv6.conf.all.forwarding=1#g" /etc/sysctl.conf # 可能没有,追加 echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf echo "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.conf echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.conf echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf echo "net.ipv6.conf.lo.disable_ipv6 = 1" >> /etc/sysctl.conf echo "net.ipv6.conf.all.forwarding = 1" >> /etc/sysctl.conf # 执行命令以应用 sysctl -p #################################################################
2、安装Docker环境(K8s运行时环境)
Docker安装参考:http://www.jiguiquan.com/?p=286
三、安装K8s
1、安装K8s核心(master + worker)
# 配置K8S的yum源 cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64 enabled=1 gpgcheck=0 repo_gpgcheck=0 gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF # 卸载旧版本 yum remove -y kubelet kubeadm kubectl # 查看可以安装的版本 yum list kubelet --showduplicates | sort -r # 安装kubelet、kubeadm、kubectl 指定版本 yum install -y kubelet-1.21.0 kubeadm-1.21.0 kubectl-1.21.0 # 开机启动kubelet systemctl enable kubelet && systemctl start kubelet
2、引导集群——初始化master节点(master执行)
############下载核心镜像 kubeadm config images list:查看需要哪些镜像########### ####封装成images.sh文件 #!/bin/bash images=( kube-apiserver:v1.21.0 kube-proxy:v1.21.0 kube-controller-manager:v1.21.0 kube-scheduler:v1.21.0 coredns:v1.8.0 etcd:3.4.13-0 pause:3.4.1 ) for imageName in ${images[@]} ; do docker pull registry.cn-hangzhou.aliyuncs.com/jgqk8s/$imageName done #####封装结束 chmod +x images.sh && ./images.sh # registry.cn-hangzhou.aliyuncs.com/jgqk8s/coredns:v1.8.0 ##注意1.21.0版本的k8s coredns镜像比较特殊,结合阿里云需要特殊处理(阿里云基础版不支持上传coredns/coredns:v1.8.0这样的镜像),所以重新打标签 docker tag registry.cn-hangzhou.aliyuncs.com/jgqk8s/coredns:v1.8.0 registry.cn-hangzhou.aliyuncs.com/jgqk8s/coredns/coredns:v1.8.0 ########kubeadm引导安装一个K8s集群得步骤######################## ########1、kubeadm init 一个master节点######################## ########2、kubeadm join 其他worker节点######################## kubeadm init \ --apiserver-advertise-address=192.168.56.20 \ --image-repository registry.cn-hangzhou.aliyuncs.com/jgqk8s \ --kubernetes-version v1.21.0 \ --service-cidr=10.96.0.0/16 \ --pod-network-cidr=192.168.0.0/16 ## 注意:pod-cidr与service-cidr # cidr 无类别域间路由(Classless Inter-Domain Routing、CIDR)————指定一个网络可达范围 # pod的子网范围 + service负载均衡网络的子网范围 + 本机ip的子网范围不能有重复域
Init成功后界面如下:
同时还贴心地给了进一步得操作指令:
######按照提示继续###### ## init完成后第一步:复制相关文件夹 ## To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config ## 导出环境变量 ## Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf ### 部署一个pod网络插件(因为K8s在这块也不专业,所以需要借助一个网络插件,我们选择calico) ## You should now deploy a pod network to the cluster. ## Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: ## https://kubernetes.io/docs/concepts/cluster-administration/addons/ ##############如下:安装calico##################### kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml ### 命令检查 kubectl get pod -A ##获取集群中所有部署好的应用Pod,我们必须等到所有应用都变为Running状态 kubectl get nodes ##查看集群所有机器的状态 Then you can join any number of worker nodes by running the following on each as root: kubeadm join 172.24.80.222:6443 --token nz9azl.9bl27pyr4exy2wz4 \ --discovery-token-ca-cert-hash sha256:4bdc81a83b80f6bdd30bb56225f9013006a45ed423f131ac256ffe16bae73a20
至此,master节点准备就绪:
3、引导集群——其他worker节点加入集群(worker执行)
## 用master生成的命令即可 kubeadm join 192.168.56.20:6443 --token oyqdg2.slzy3k1c32xxb2me \ --discovery-token-ca-cert-hash sha256:a362fafa4c8b232eed39d8d99d7cd9906321771e22f30073eea5620c3e29ab7f ##初始化master节点时候生成的join命令,有效期为2小时,过期怎么办?(下面两个命令都可以) [root@k8s-01 ~]# kubeadm token create --print-join-command kubeadm join 192.168.56.20:6443 --token i9ai3z.zi85qntgm3q9h3xb --discovery-token-ca-cert-hash sha256:a362fafa4c8b232eed39d8d99d7cd9906321771e22f30073eea5620c3e29ab7f [root@k8s-01 ~]# kubeadm token create --ttl 0 --print-join-command kubeadm join 192.168.56.20:6443 --token hdz6s9.z9811zi2mf6pl2xg --discovery-token-ca-cert-hash sha256:a362fafa4c8b232eed39d8d99d7cd9906321771e22f30073eea5620c3e29ab7f
至此,worker节点就也加入到master初始化的集群中了:
4、验证集群,为worker节点打上标签
#获取所有节点 kubectl get nodes #给节点打标签 ## k8s中万物皆对象。node:机器 Pod:应用容器 ###加标签 《h1》 kubectl label node k8s-02 node-role.kubernetes.io/worker='' ###去标签 kubectl label node k8s-02 node-role.kubernetes.io/worker- ## k8s集群,机器重启了会自动再加入集群,master重启了会自动再加入集群控制中心
5、设置ipvs模式
k8s整个集群为了访问通;默认是用iptables,会导致性能下降;
(kube-proxy会在集群之间同步iptables的内容,集群一大后,同步iptables特别浪费性能)
#1、查看默认kube-proxy 使用的模式 kubectl logs -n kube-system kube-proxy-28xv4 #2、需要修改 kube-proxy 的配置文件,修改mode 为ipvs。默认iptables,但是集群大了以后就很慢 kubectl edit cm kube-proxy -n kube-system 修改如下 ipvs: excludeCIDRs: null minSyncPeriod: 0s scheduler: "" strictARP: false syncPeriod: 30s kind: KubeProxyConfiguration metricsBindAddress: 127.0.0.1:10249 mode: "ipvs" ###修改了kube-proxy的配置,为了让重新生效,需要杀掉以前的Kube-proxy kubectl get pod -A -o wide|grep kube-proxy #列出所有的kube-proxy kubectl delete pod kube-proxy-pqgnt -n kube-system #删除查到的kube-proxy ### 修改完成后可以重启kube-proxy以生效
当我们删除掉旧的kube-proxy后,k8s会自动为我们启动新的kube-proxy!
6、让其他客户端也能够操作集群
刚安装完成集群后,默认情况下,其他worker节点是没有办法执行集群的操作的,如果我们想执行,那么还需要做以下操作:
#1、master获取管理员配置 cat /etc/kubernetes/admin.conf #2、其他节点创建保存 mkdir .kube vi ~/.kube/config #3、重新测试使用
之后再次尝试在worker节点操作集群:
四、K8s入门demo——部署一个helloworld应用
1、启动两个nginx服务
# kubectl create 帮我们创建k8s集群中的一些对象 kubectl create --help kubectl create deployment 这次部署的名字 --image=应用的镜像 #Create a deployment named my-nginx that runs the nginx image kubectl create deployment my-nginx --image=nginx ##最终在一个机器上有pod、这个pod其实本质里面就是一个容器 k8s_nginx_my-nginx-6b74b79f57-snlr4_default_dbeac79e-1ce9-42c9-bc59-c8ca0412674b_0 ### k8s_镜像(nginx)_pod名(my-nginx-6b74b79f57-snlr4)_容器名(default_dbeac79e-1ce9-42c9-bc59-c8ca0412674b_0) # Create a deployment with command kubectl create deployment my-nginx --image=nginx -- date # Create a deployment named my-nginx that runs the nginx image with 3 replicas. kubectl create deployment my-nginx --image=nginx --replicas=3 # Create a deployment named my-nginx that runs the nginx image and expose port 80. kubectl create deployment my-nginx --image=nginx --port=80
我们创建2各my-nginx:
[root@k8s-01 ~]# kubectl create deployment my-nginx --image=nginx --replicas=2 deployment.apps/my-nginx created [root@k8s-01 ~]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-nginx-6b74b79f57-lb72p 1/1 Running 0 103s 10.244.179.7 k8s-02 <none> <none> my-nginx-6b74b79f57-qzls7 1/1 Running 0 103s 10.244.179.8 k8s-02 <none> <none>
2、解决问题:无法进入已经创建的Pod内部
当我们尝试进入已经创建好的pod内部时,报错,找不到Pod
[root@k8s-02 .kube]# kubectl exec my-nginx-6b74b79f57-4q9xj -- /bin/bash error: unable to upgrade connection: pod does not exist
这应该是我们创建的Vagrant虚拟机的双网卡问题导致的
解决办法:
在 /etc/hosts 配置IP地址和主机名映射(主机点和所有子节点)
vim /etc/hosts # 添加 IP地址和主机名映射, 例如 192.168.56.21 k8s-02 # 重启docker systemctl restart docker
之后就可以exec访问了:
借助kubeadm引导安装集群的工作就完成了,是不是很简单。
篇幅问题,如果又k8s使用相关的内容,会另起文章!
补充1——K8s得卸载:
kubeadm reset -f rm -rf ~/.kube/ rm -rf /etc/kubernetes/ rm -rf /etc/systemd/system/kubelet.service.d rm -rf /etc/systemd/system/kubelet.service rm -rf /usr/bin/kube* rm -rf /etc/cni rm -rf /opt/cni rm -rf /var/lib/etcd rm -rf /var/etcd
补充2——K8s官方Dashboard可视化界面的安装(官方yaml文件有坑)
参考官方文档:https://kubernetes.io/zh/docs/tasks/access-application-cluster/web-ui-dashboard/
参考github:https://github.com/kubernetes/dashboard
1、wget拉取官方提供的yaml文件:
wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.4.0/aio/deploy/recommended.yaml
访问不了就加添加hosts
2、将负载均衡网络设置为NodePort(增加行)
type: NodePort 在每一个节点上暴露一个端口,这样我们使用节点的公网ip + 端口,即可直接访问到该负载均衡网络!
3、开始应用此 yaml 描述文件:
kubectl apply -f recommended.yaml
[root@k8s-01 ~]# kubectl get service -A NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 23h default nginx-service NodePort 10.1.17.16 <none> 80:32600/TCP 11h kube-system kube-dns ClusterIP 10.1.0.10 <none> 53/UDP,53/TCP,9153/TCP 23h kubernetes-dashboard dashboard-metrics-scraper ClusterIP 10.1.47.92 <none> 8000/TCP 3m kubernetes-dashboard kubernetes-dashboard NodePort 10.1.11.182 <none> 443:30534/TCP 3m
所以我们就可以在我们自己的电脑上访问任意一台虚拟机的30534端口,即可访问到Dashboard界面(https://192.168.56.20:30534):
3、获取访问的令牌进入:
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}') # Token结果记录: eyJhbGciOiJSUzI1NiIsImtpZCI6ImZkVUNDckU3ZkM0M0k1YWgzQ0VCQzhVSWtZQmpwaEhRNGdyakdHUE02UlUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZC10b2tlbi1zd2Z0aCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImZlMWZlODg3LWZkNzQtNDc1NC05YzQwLWMzODgzOTlkZmEyMyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlcm5ldGVzLWRhc2hib2FyZDprdWJlcm5ldGVzLWRhc2hib2FyZCJ9.O3nbjZMu8mBtjN0hXNQESN6ksYOGbFj5_h48hX1lRATZE5z8VRx0JYZ3nEB6Q48a3E8RzjxrSom_Qpo8VoPK3AIF3rnO4OnBk34Y274q_aB6T9P9iKXsZemfKRcA05BTQWNlvxygjWTsOVvVd63RIBAOk9MQOw9Ly4_B1WB900wCfGJgS7cLPyRVwlJ1c2ea3lbDplV9SSPn0_zc3vQXQYgGgG7hY2nCt6oDj4vHrc4lPFN9JZf3r24xHYpQbnAsbjj2Xwf1gKD7-3mnp6OsJr-eu5nu5EzzxG-a5HrSvQlShbxTiw9JBWhsky_xe2v0H6GOjKMQgxcHZJNGlpGYpA
有了token令牌,我们就可以正常访问Dashboard啦!但是当我们进入后,会缺少rbac的权限:
4、开启rbac权限:
参考:https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/README.md
创建一个dashboard-admin.yaml:
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kubernetes-dashboard namespace: kubernetes-dashboard roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: kubernetes-dashboard namespace: kubernetes-dashboard
执行应用(如果直接执行不成功,就先delete一下):
[root@k8s-01 ~]# kubectl delete -f dashboard-admin.yaml clusterrolebinding.rbac.authorization.k8s.io "kubernetes-dashboard" deleted [root@k8s-01 ~]# kubectl apply -f dashboard-admin.yaml clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
之后,我们再次进入Dashboard,就可以正常访问了:
补充三、Kubernetes的核心文件在哪儿?
kubeadm安装的集群。二进制后来就是 yum install etcd api-server
认识核心文件夹 /etc/kubernetes . 以Pod方式安装的核心组件。
etcd,api-server,scheduler。(安装k8s的时候,yum kubeadm kubelet kubectl)
回顾集群安装的时候,为什么只有master节点的kubectl可以操作集群
kubelet额外参数配置 /etc/sysconfig/kubelet;kubelet配置位置 /var/lib/kubelet/config.yaml
kubectl的所有命令参考:
命令参考:https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands
pdf命令实战:https://github.com/dennyzhang/cheatsheet-kubernetes-A4/blob/master/cheatsheet-kubernetes-A4.pdf
补充四、配置命令行操作kubectl时候的自动补全
参考文档:https://kubernetes.io/zh/docs/tasks/tools/included/optional-kubectl-configs-bash-linux/
# 安装 yum install bash-completion # 自动补全 echo 'source <(kubectl completion bash)' >>~/.bashrc kubectl completion bash >/etc/bash_completion.d/kubectl source /usr/share/bash-completion/bash_completion