You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

all-in-one.sh 14 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. #!/bin/bash
  2. # Copyright 2021 The KubeEdge Authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. # This script installs a all-in-one Sedna environment, including:
  16. # - A Kubernetes v1.21 cluster with multi worker nodes, default none worker node.
  17. # - KubeEdge with multi nodes, default is latest KubeEdge and one edge node.
  18. # - Sedna, default is latest release version.
  19. #
  20. # It requires you:
  21. # - 2 CPUs or more
  22. # - 2GB+ free memory, depends on node number setting
  23. # - 10GB+ free disk space
  24. # - Internet connection(docker hub, github etc.)
  25. # - Linux platform, such as ubuntu/centos
  26. # - Docker 17.06+
  27. #
  28. # Advanced options, influential env vars:
  29. #
  30. # NUM_CLOUD_WORKER_NODES| optional | The number of cloud worker nodes, default 0
  31. # NUM_EDGE_NODES | optional | The number of KubeEdge nodes, default 1
  32. # KUBEEDGE_VERSION | optional | The KubeEdge version to be installed.
  33. # if not specified, it try to get latest version or v1.8.0
  34. # SEDNA_VERSION | optional | The Sedna version to be installed.
  35. # if not specified, it will get latest release or v0.4.1
  36. # CLUSTER_NAME | optional | The all-in-one cluster name, default 'sedna-mini'
  37. # FORCE_INSTALL_SEDNA | optional | If 'true', force reinstall Sedna, default false.
  38. # NODE_IMAGE | optional | Custom node image
  39. # REUSE_EDGE_CONTAINER | optional | Whether reuse edge node containers or not, default is true
  40. set -o errexit
  41. set -o nounset
  42. set -o pipefail
  43. DEFAULT_SEDNA_VERSION=v0.4.1
  44. DEFAULT_KUBEEDGE_VERSION=v1.8.0
  45. DEFAULT_NODE_IMAGE_VERSION=v1.21.1
  46. function prepare_env() {
  47. : ${CLUSTER_NAME:=sedna-mini}
  48. # here not use := because it ignore the error of get_latest_version command
  49. if [ -z "${KUBEEDGE_VERSION:-}" ]; then
  50. KUBEEDGE_VERSION=$(get_latest_version kubeedge/kubeedge $DEFAULT_KUBEEDGE_VERSION)
  51. fi
  52. if [ -z "${SEDNA_VERSION:-}" ]; then
  53. SEDNA_VERSION=$(get_latest_version kubeedge/sedna $DEFAULT_SEDNA_VERSION)
  54. fi
  55. : ${NUM_CLOUD_WORKER_NODES:=0}
  56. : ${NUM_EDGE_NODES:=1}
  57. : ${ALLINONE_NODE_IMAGE:=kubeedge/sedna-allinone-node:$DEFAULT_NODE_IMAGE_VERSION}
  58. readonly MAX_CLOUD_WORKER_NODES=2
  59. readonly MAX_EDGE_WORKER_NODES=3
  60. # TODO: find a better way to figure this kind control plane
  61. readonly CONTROL_PLANE_NAME=${CLUSTER_NAME}-control-plane
  62. readonly CLOUD_WORKER_NODE_NAME=${CLUSTER_NAME}-worker
  63. # cloudcore default websocket port
  64. : ${CLOUDCORE_WS_PORT:=10000}
  65. # cloudcore default cert port
  66. : ${CLOUDCORE_CERT_PORT:=10002}
  67. # for debug purpose
  68. : ${RETAIN_CONTAINER:=}
  69. # use existing edge node containers
  70. # default is true
  71. : ${REUSE_EDGE_CONTAINER:=true}
  72. # force install sedna control plane
  73. # default is false
  74. : ${FORCE_INSTALL_SEDNA:=false}
  75. # The docker network for edge nodes to separate the network of control plane.
  76. # Since `kind` CNI doesn't support edge node, here just use the network 'kind'.
  77. # TODO(llhuii): find a way to use the default docker network 'bridge'.
  78. : ${EDGE_NODE_NETWORK:=kind}
  79. validate_env
  80. }
  81. function validate_env() {
  82. ((NUM_CLOUD_WORKER_NODES<=MAX_CLOUD_WORKER_NODES)) || {
  83. log_fault "Only support NUM_CLOUD_WORKER_NODES at most $MAX_CLOUD_WORKER_NODES"
  84. }
  85. ((NUM_EDGE_NODES<=MAX_EDGE_WORKER_NODES)) || {
  86. log_fault "Only support NUM_EDGE_NODES at most $MAX_EDGE_WORKER_NODES"
  87. }
  88. }
  89. function _log() {
  90. local level=$1
  91. shift
  92. timestamp=$(date +"[$level%m%d %H:%M:%S.%3N]")
  93. echo "$timestamp $@"
  94. }
  95. function log_fault() {
  96. _log E "$@" >&2
  97. exit 2
  98. }
  99. function log_error() {
  100. _log E "$@" >&2
  101. }
  102. function log_info() {
  103. _log I "$@"
  104. }
  105. function gen_kind_config() {
  106. cat <<EOF
  107. kind: Cluster
  108. apiVersion: kind.x-k8s.io/v1alpha4
  109. name: $CLUSTER_NAME
  110. nodes:
  111. - role: control-plane
  112. image: $ALLINONE_NODE_IMAGE
  113. # expose kubeedge cloudcore
  114. extraPortMappings:
  115. - containerPort: $CLOUDCORE_WS_PORT
  116. - containerPort: $CLOUDCORE_CERT_PORT
  117. EOF
  118. for((i=0;i<NUM_CLOUD_WORKER_NODES;i++)); do
  119. cat <<EOF
  120. - role: worker
  121. image: $ALLINONE_NODE_IMAGE
  122. EOF
  123. done
  124. }
  125. function patch_kindnet() {
  126. # Since in edge node, we just use containerd instead of docker, this requires CNI,
  127. # And `kindnet` is the CNI in kind, requires `InClusterConfig`
  128. # which would require KUBERNETES_SERVICE_HOST/KUBERNETES_SERVICE_PORT environment variables.
  129. # But edgecore(up to 1.8.0) does not inject these environments.
  130. # Here make a patch: can be any value
  131. run_in_control_plane kubectl set env -n kube-system daemonset/kindnet KUBERNETES_SERVICE_HOST=10.96.0.1 KUBERNETES_SERVICE_PORT=443
  132. }
  133. function create_k8s_cluster() {
  134. if kind get clusters | grep -qx -F "$CLUSTER_NAME"; then
  135. log_info "The k8s cluster $CLUSTER_NAME already exists, and just use it!"
  136. log_info "If you want to recreate one, just run \`$0 clean\`."
  137. return
  138. fi
  139. local extra_options=(--wait 90s)
  140. [ -n "$RETAIN_CONTAINER" ] && extra_options+=(--retain)
  141. gen_kind_config | kind create cluster ${extra_options[@]} --config -
  142. }
  143. function clean_k8s_cluster() {
  144. kind delete cluster --name ${CLUSTER_NAME}
  145. }
  146. function run_in_control_plane() {
  147. docker exec -i $CONTROL_PLANE_NAME "$@"
  148. }
  149. function get_control_plane_ip() {
  150. # https://stackoverflow.com/a/20686101
  151. docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $CONTROL_PLANE_NAME
  152. }
  153. function get_control_plane_exposed_port() {
  154. local container_port=$1
  155. docker inspect -f "{{(index (index .NetworkSettings.Ports \"${container_port}/tcp\") 0).HostPort}}" $CONTROL_PLANE_NAME
  156. }
  157. function setup_control_kubeconfig() {
  158. run_in_control_plane bash -euc "
  159. # copy kube config file
  160. mkdir -p ~/.kube
  161. cp /etc/kubernetes/admin.conf ~/.kube/config
  162. "
  163. }
  164. function setup_cloudcore() {
  165. # keadm already built into control plane
  166. CLOUDCORE_LOCAL_IP=$(get_control_plane_ip)
  167. # Use default docker network for edge nodes to separate the network of control plane which uses the defined network 'kind'
  168. CLOUDCORE_EXPOSED_IP=$(get_docker_network_gw $EDGE_NODE_NETWORK)
  169. CLOUDCORE_EXPOSED_WS_PORT=$(get_control_plane_exposed_port $CLOUDCORE_WS_PORT)
  170. CLOUDCORE_EXPOSED_CERT_PORT=$(get_control_plane_exposed_port $CLOUDCORE_CERT_PORT)
  171. CLOUDCORE_ADVERTISE_ADDRESSES=$CLOUDCORE_LOCAL_IP,$CLOUDCORE_EXPOSED_IP
  172. CLOUDCORE_EXPOSED_ADDR=$CLOUDCORE_EXPOSED_IP:$CLOUDCORE_EXPOSED_WS_PORT
  173. # keadm accepts version format: 1.8.0
  174. local version=${KUBEEDGE_VERSION/v}
  175. run_in_control_plane bash -euc "
  176. # install cloudcore
  177. pgrep cloudcore >/dev/null || keadm init --kubeedge-version=$version --advertise-address=$CLOUDCORE_ADVERTISE_ADDRESSES"'
  178. # wait token to be created
  179. exit_code=1
  180. TIMEOUT=30 # in seconds
  181. for((i=1;i<=TIMEOUT; i++)); do
  182. keadm gettoken >/dev/null 2>&1 && exit_code=0 && break
  183. echo -ne "Waiting cloudcore to generate token, $i seconds...\r"
  184. sleep 1
  185. done
  186. echo
  187. if [ $exit_code -gt 0 ]; then
  188. log_lines=50
  189. tail -$log_lines /var/log/kubeedge/cloudcore.log | sed "s/^/ /"
  190. echo "Timeout to wait cloudcore, above are the last $log_lines log of cloudcore."
  191. fi
  192. exit $exit_code
  193. '
  194. KUBEEDGE_TOKEN=$(run_in_control_plane keadm gettoken)
  195. }
  196. function gen_cni_config() {
  197. cat <<EOF
  198. {
  199. "cniVersion": "0.3.1",
  200. "name": "edgecni",
  201. "plugins": [
  202. {
  203. "type": "ptp",
  204. "ipMasq": false,
  205. "ipam": {
  206. "type": "host-local",
  207. "dataDir": "/run/cni-ipam-state",
  208. "routes": [
  209. {
  210. "dst": "0.0.0.0/0"
  211. }
  212. ],
  213. "ranges": [
  214. [
  215. {
  216. "subnet": "10.244.0.0/24"
  217. }
  218. ]
  219. ]
  220. },
  221. "mtu": 1500
  222. },
  223. {
  224. "type": "portmap",
  225. "capabilities": {
  226. "portMappings": true
  227. }
  228. }
  229. ]
  230. }
  231. EOF
  232. }
  233. function create_and_setup_edgenodes() {
  234. for((i=0;i<NUM_EDGE_NODES;i++)); do
  235. log_info "Installing $i-th edge node..."
  236. local containername=sedna-mini-edge$i
  237. local hostname=edge$i
  238. local label=sedna.io=sedna-mini-edge
  239. # Many tricky arguments are from the kind code
  240. # https://github.com/kubernetes-sigs/kind/blob/4910c3e221a858e68e29f9494170a38e1c4e8b80/pkg/cluster/internal/providers/docker/provision.go#L148
  241. local run_cmds=(
  242. docker run
  243. --network "$EDGE_NODE_NETWORK"
  244. --hostname "$hostname"
  245. --name "$containername"
  246. --label $label
  247. --privileged
  248. --security-opt seccomp=unconfined
  249. --security-opt apparmor=unconfined
  250. --tmpfs /tmp
  251. --tmpfs /run
  252. --volume /var
  253. # some k8s things want to read /lib/modules
  254. --volume /lib/modules:/lib/modules:ro
  255. --restart=on-failure:1
  256. --tty
  257. --detach $ALLINONE_NODE_IMAGE
  258. )
  259. local existing_id=$(docker ps -qa --filter name=$containername --filter label=$label)
  260. if [ -n "$existing_id" ]; then
  261. if [ "${REUSE_EDGE_CONTAINER,,}" = true ] ; then
  262. log_info "Use existing container for ''$containername'"
  263. log_info "If not your attention, you can do:"
  264. log_info " 1) set REUSE_EDGE_CONTAINER=false"
  265. log_info " Or 2) clean it first."
  266. log_info "And rerun this script."
  267. # start in case stopped
  268. docker start $containername
  269. else
  270. log_error "The container named $containername already exists, you can do:"
  271. log_error " 1) set REUSE_EDGE_CONTAINER=true"
  272. log_error " Or 2) clean it first."
  273. log_fault "And rerun this script."
  274. fi
  275. else
  276. # does not exist, create one container for this edge
  277. "${run_cmds[@]}"
  278. fi
  279. # install edgecore using keadm join
  280. local version=${KUBEEDGE_VERSION/v}
  281. docker exec -i $containername bash -uec "
  282. pgrep edgecore >/dev/null || {
  283. keadm join \
  284. --cloudcore-ipport=${CLOUDCORE_EXPOSED_ADDR} \
  285. --certport=${CLOUDCORE_EXPOSED_CERT_PORT} \
  286. --token=$KUBEEDGE_TOKEN \
  287. --kubeedge-version '$version' \
  288. --edgenode-name '$hostname' \
  289. --remote-runtime-endpoint unix:///var/run/containerd/containerd.sock \
  290. --runtimetype remote
  291. # set imageGCHighThreshold to 100% for no image gc
  292. sed -i 's/imageGCHighThreshold:.*/imageGCHighThreshold: 100/' /etc/kubeedge/config/edgecore.yaml &&
  293. systemctl restart edgecore ||
  294. true # ignore the error
  295. }
  296. "
  297. # fix cni config file
  298. gen_cni_config | docker exec -i $containername tee /etc/cni/net.d/10-edgecni.conflist >/dev/null
  299. done
  300. }
  301. function clean_edgenodes() {
  302. for cid in $(docker ps -a --filter label=sedna.io=sedna-mini-edge -q); do
  303. docker stop $cid; docker rm $cid
  304. done
  305. }
  306. function get_docker_network_gw() {
  307. docker network inspect ${1-bridge} --format='{{(index .IPAM.Config 0).Gateway}}'
  308. }
  309. function setup_cloud() {
  310. create_k8s_cluster
  311. patch_kindnet
  312. setup_control_kubeconfig
  313. setup_cloudcore
  314. }
  315. function clean_cloud() {
  316. clean_k8s_cluster
  317. }
  318. function setup_edge() {
  319. create_and_setup_edgenodes
  320. }
  321. function clean_edge() {
  322. clean_edgenodes
  323. }
  324. function install_sedna() {
  325. local gm_node=$CONTROL_PLANE_NAME
  326. if run_in_control_plane kubectl get ns sedna; then
  327. if [ "$FORCE_INSTALL_SEDNA" != true ]; then
  328. log_info '"sedna" namespace already exists, no install Sedna control components.'
  329. log_info 'If want to reinstall them, you can remove it by `kubectl delete ns sedna` or set FORCE_INSTALL_SEDNA=true!'
  330. log_info
  331. return
  332. fi
  333. run_in_control_plane bash -ec "
  334. curl https://raw.githubusercontent.com/kubeedge/sedna/main/scripts/installation/install.sh | SEDNA_GM_NODE=$gm_node SEDNA_ACTION=clean SEDNA_VERSION=$SEDNA_VERSION bash -
  335. "
  336. fi
  337. log_info "Installing Sedna Control Components..."
  338. run_in_control_plane bash -ec "
  339. kubectl taint node $gm_node node-role.kubernetes.io/master- || true
  340. curl https://raw.githubusercontent.com/kubeedge/sedna/main/scripts/installation/install.sh | SEDNA_GM_NODE=$gm_node SEDNA_ACTION=create bash -
  341. "
  342. }
  343. function get_latest_version() {
  344. # get the latest version of specified gh repo
  345. local repo=${1} default_version=${2:-}
  346. # output of this latest page:
  347. # ...
  348. # "tag_name": "v1.0.0",
  349. # ...
  350. # Sometimes this will reach rate limit
  351. # https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting
  352. local url=https://api.github.com/repos/$repo/releases/latest
  353. if ! curl --fail -s $url | awk '/"tag_name":/&&$0=$2' | sed 's/[",]//g'; then
  354. log_error "Error to get latest version of $repo: $(curl -s $url | head)"
  355. [ -n "$default_version" ] && {
  356. log_error "Fall back to default version: $default_version"
  357. echo $default_version
  358. }
  359. fi
  360. }
  361. function arch() {
  362. local arch=$(uname -m)
  363. case "$arch" in
  364. x86_64) arch=amd64;;
  365. *);;
  366. esac
  367. echo "$arch"
  368. }
  369. function _download_tool() {
  370. local name=$1 url=$2
  371. local file=/usr/local/bin/$name
  372. curl -Lo $file $url
  373. chmod +x $file
  374. }
  375. function check_command_exists() {
  376. type $1 >/dev/null 2>&1
  377. }
  378. function ensure_tool() {
  379. local command=$1 download_url=$2
  380. if check_command_exists $command; then
  381. return
  382. fi
  383. _download_tool $command $download_url
  384. }
  385. function ensure_kind() {
  386. local version=${KIND_VERSION:-0.11.1}
  387. ensure_tool kind https://kind.sigs.k8s.io/dl/v${version/v}/kind-linux-$(arch)
  388. }
  389. function ensure_kubectl() {
  390. local version=${KUBECTL_VERSION:-1.21.0}
  391. ensure_tool kubectl https://dl.k8s.io/release/v${version/v}/bin/linux/$(arch)/kubectl
  392. }
  393. function ensure_tools() {
  394. ensure_kind
  395. ensure_kubectl
  396. }
  397. function main() {
  398. ensure_tools
  399. prepare_env
  400. action=${1-create}
  401. case "$action" in
  402. create)
  403. setup_cloud
  404. setup_edge
  405. install_sedna
  406. log_info "Mini Sedna is created successfully"
  407. ;;
  408. delete|clean)
  409. clean_edge
  410. clean_cloud
  411. log_info "Mini Sedna is uninstalled successfully"
  412. ;;
  413. # As a source file, noop
  414. __source__)
  415. ;;
  416. *)
  417. log_fault "Unknown action $action"
  418. ;;
  419. esac
  420. }
  421. main "$@"