scope 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. #!/bin/sh
  2. set -eu
  3. ARGS="$*"
  4. SCRIPT_VERSION="(unreleased version)"
  5. if [ "$SCRIPT_VERSION" = "(unreleased version)" ]; then
  6. IMAGE_VERSION=latest
  7. else
  8. IMAGE_VERSION="$SCRIPT_VERSION"
  9. fi
  10. IMAGE_VERSION=${VERSION:-$IMAGE_VERSION}
  11. DOCKERHUB_USER=${DOCKERHUB_USER:-weaveworks}
  12. SCOPE_IMAGE_NAME="$DOCKERHUB_USER/scope"
  13. SCOPE_IMAGE="$SCOPE_IMAGE_NAME:$IMAGE_VERSION"
  14. # Careful: it's easy to operate on (e.g. stop) the wrong scope instance
  15. # when SCOPE{_APP,}_CONTAINER_NAME values differ between runs. Handle
  16. # with care.
  17. SCOPE_CONTAINER_NAME="${SCOPE_CONTAINER_NAME:-weavescope}"
  18. SCOPE_APP_CONTAINER_NAME="${SCOPE_APP_CONTAINER_NAME:-weavescope-app}"
  19. IP_REGEXP="[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"
  20. IP_ADDR_CMD="find /sys/class/net -type l | xargs -n1 basename | grep -vE 'docker|veth|lo' | \
  21. xargs -n1 ip addr show | grep inet | awk '{ print \$2 }' | grep -oE '$IP_REGEXP'"
  22. LISTENING_IP_ADDR_CMD="for I in \$( $IP_ADDR_CMD ); do if curl -m 1 -s \${I}:4040 > /dev/null ; then echo \${I}; fi; done"
  23. WEAVESCOPE_DOCKER_ARGS=${WEAVESCOPE_DOCKER_ARGS:-}
  24. # When docker daemon is running with User Namespace enabled, this tool will run into errors:
  25. # "Privileged mode is incompatible with user namespaces" for `docker run --privileged`
  26. # "Cannot share the host's network namespace when user namespaces are enabled" for `docker run --net=host`
  27. # To avoid above errors, use `--userns=host` option to let container use host User Namespace.
  28. # This option(saved in $USERNS_HOST) will be inserted ONLY IF docker support `--userns` option.
  29. USERNS_HOST=""
  30. docker run --help | grep -q -- --userns && USERNS_HOST="--userns=host"
  31. usage() {
  32. name=$(basename "$0")
  33. cat >&2 <<-EOF
  34. Usage:
  35. $name launch {OPTIONS} {PEERS} - Launch Scope
  36. $name stop - Stop Scope
  37. $name command - Print the docker command used to start Scope
  38. $name help - Print usage info
  39. $name version - Print version info
  40. PEERS are of the form HOST[:PORT]
  41. HOST may be an ip or hostname.
  42. PORT defaults to 4040.
  43. Launch options:
  44. EOF
  45. docker run --rm --entrypoint=/home/weave/scope "$SCOPE_IMAGE" -h >&2
  46. }
  47. usage_and_die() {
  48. usage
  49. exit 1
  50. }
  51. [ $# -gt 0 ] || usage_and_die
  52. COMMAND=$1
  53. shift 1
  54. check_docker_access() {
  55. # Extract socket path
  56. DOCKER_SOCK_FILE=""
  57. if [ -z "${DOCKER_HOST+x}" ]; then
  58. DOCKER_SOCK_FILE="/var/run/docker.sock"
  59. else
  60. WITHOUT_PREFIX="${DOCKER_HOST#unix://}"
  61. if [ "$WITHOUT_PREFIX" != "$DOCKER_HOST" ]; then
  62. DOCKER_SOCK_FILE="$WITHOUT_PREFIX"
  63. fi
  64. fi
  65. # shellcheck disable=SC2166
  66. if [ \( -n "$DOCKER_SOCK_FILE" \) -a \( ! -w "$DOCKER_SOCK_FILE" \) ]; then
  67. echo "ERROR: cannot write to docker socket: $DOCKER_SOCK_FILE" >&2
  68. echo "change socket permissions or try using sudo" >&2
  69. exit 1
  70. fi
  71. }
  72. # - The image embeds the weave script & Docker 1.13.1 client (mimicking a 1.10 client)
  73. # - Weave needs 1.10.0 now (image pulling changes)
  74. MIN_DOCKER_VERSION=1.10.0
  75. check_docker_version() {
  76. if ! DOCKER_VERSION=$(docker -v | sed -n 's%^Docker version \([0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\).*$%\1%p') \
  77. || [ -z "$DOCKER_VERSION" ]; then
  78. echo "ERROR: Unable to parse docker version" >&2
  79. exit 1
  80. fi
  81. DOCKER_VERSION_MAJOR=$(echo "$DOCKER_VERSION" | cut -d. -f 1)
  82. DOCKER_VERSION_MINOR=$(echo "$DOCKER_VERSION" | cut -d. -f 2)
  83. DOCKER_VERSION_PATCH=$(echo "$DOCKER_VERSION" | cut -d. -f 3)
  84. MIN_DOCKER_VERSION_MAJOR=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 1)
  85. MIN_DOCKER_VERSION_MINOR=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 2)
  86. MIN_DOCKER_VERSION_PATCH=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 3)
  87. # shellcheck disable=SC2166
  88. if [ \( "$DOCKER_VERSION_MAJOR" -lt "$MIN_DOCKER_VERSION_MAJOR" \) -o \
  89. \( "$DOCKER_VERSION_MAJOR" -eq "$MIN_DOCKER_VERSION_MAJOR" -a \
  90. \( "$DOCKER_VERSION_MINOR" -lt "$MIN_DOCKER_VERSION_MINOR" -o \
  91. \( "$DOCKER_VERSION_MINOR" -eq "$MIN_DOCKER_VERSION_MINOR" -a \
  92. \( "$DOCKER_VERSION_PATCH" -lt "$MIN_DOCKER_VERSION_PATCH" \) \) \) \) ]; then
  93. echo "ERROR: scope requires Docker version $MIN_DOCKER_VERSION or later; you are running $DOCKER_VERSION" >&2
  94. exit 1
  95. fi
  96. }
  97. check_probe_only() {
  98. echo "${ARGS}" | grep -q -E -e "--no-app|--service-token|--probe-only"
  99. }
  100. check_listen_address_arg() {
  101. echo "${ARGS}" | grep -q -E -e "--app\\.http\\.address"
  102. }
  103. check_docker_for_mac() {
  104. [ "$(uname)" = "Darwin" ] \
  105. && [ -S /var/run/docker.sock ] \
  106. && [ ! "${DOCKER_HOST+x}" = x ] \
  107. && [ "${HOME+x}" = x ] \
  108. && [ -d "${HOME}/Library/Containers/com.docker.docker/Data" ]
  109. }
  110. # Check that a container named $1 with image $2 is not running
  111. check_not_running() {
  112. case $(docker inspect --format='{{.State.Running}} {{.Config.Image}}' "$1" 2>/dev/null) in
  113. "true $2")
  114. echo "$1 is already running." >&2
  115. exit 1
  116. ;;
  117. "true $2:"*)
  118. echo "$1 is already running." >&2
  119. exit 1
  120. ;;
  121. "false $2")
  122. docker rm "$1" >/dev/null
  123. ;;
  124. "false $2:"*)
  125. docker rm "$1" >/dev/null
  126. ;;
  127. true*)
  128. echo "Found another running container named '$1'. Aborting." >&2
  129. exit 1
  130. ;;
  131. false*)
  132. echo "Found another container named '$1'. Aborting." >&2
  133. exit 1
  134. ;;
  135. esac
  136. }
  137. check_plugins_dir() {
  138. # If plugins dir exists for Docker containers then we will mount it
  139. # (the context for Docker might be different to that for this script, e.g. when using Docker for Mac)
  140. if docker run $USERNS_HOST --rm --entrypoint=/bin/sh \
  141. -v /var/run:/var/run \
  142. "$SCOPE_IMAGE" -c "test -d /var/run/scope/plugins"; then
  143. PLUGINS_DIR_EXISTS=true
  144. fi
  145. }
  146. docker_args() {
  147. echo --privileged $USERNS_HOST --net=host --pid=host \
  148. -v /var/run/docker.sock:/var/run/docker.sock \
  149. -v /sys/kernel/debug:/sys/kernel/debug \
  150. -e CHECKPOINT_DISABLE
  151. # shellcheck disable=SC2039
  152. [ -n "${PLUGINS_DIR_EXISTS:-}" ] && echo -v /var/run/scope/plugins:/var/run/scope/plugins
  153. }
  154. launch_command() {
  155. # shellcheck disable=SC2046,SC2086
  156. echo docker run -d --name="$SCOPE_CONTAINER_NAME" $(docker_args) \
  157. $WEAVESCOPE_DOCKER_ARGS "$SCOPE_IMAGE" --probe.docker=true
  158. }
  159. launch_docker4mac_app_command() {
  160. # shellcheck disable=SC2086
  161. echo docker run -d --name="$SCOPE_APP_CONTAINER_NAME" \
  162. -e CHECKPOINT_DISABLE \
  163. -p 0.0.0.0:4040:4040 \
  164. $WEAVESCOPE_DOCKER_ARGS "$SCOPE_IMAGE" --no-probe
  165. }
  166. launch() {
  167. check_not_running "$SCOPE_CONTAINER_NAME" "$SCOPE_IMAGE_NAME"
  168. docker rm -f "$SCOPE_CONTAINER_NAME" >/dev/null 2>&1 || true
  169. $(launch_command) "$@"
  170. echo "Scope probe started"
  171. }
  172. print_app_endpoints() {
  173. HOST_SUFFIX=""
  174. if [ -n "${DOCKER_HOST+x}" ]; then
  175. DOCKER_HOSTNAME=$(run_in_scope_container hostname)
  176. HOST_SUFFIX=" of host $DOCKER_HOSTNAME"
  177. fi
  178. echo "Weave Scope is listening at the following URL(s)${HOST_SUFFIX}:" >&2
  179. for ip in "$@"; do
  180. echo " * http://$ip:4040/" >&2
  181. done
  182. }
  183. dry_run() {
  184. # Do a dry run of scope in the foreground, so it can parse args etc
  185. # avoiding the entrypoint script in the process.
  186. # shellcheck disable=SC2046
  187. docker run --rm --entrypoint=/home/weave/scope $(docker_args) "$SCOPE_IMAGE" --dry-run "$@"
  188. }
  189. run_in_scope_container() {
  190. docker run --rm $USERNS_HOST --net=host --entrypoint /bin/sh "$SCOPE_IMAGE" -c "$1"
  191. }
  192. # Wait for the scope app to start listening on localhost:4040
  193. wait_for_http() {
  194. for seconds in $(seq 5); do
  195. if run_in_scope_container "curl -m 1 -s localhost:4040" >/dev/null; then
  196. break
  197. fi
  198. sleep 1
  199. done
  200. if [ "$seconds" -eq 5 ]; then
  201. echo "The Scope App is not responding. Consult the container logs for further details."
  202. exit 1
  203. fi
  204. }
  205. check_docker_access
  206. check_docker_version
  207. case "$COMMAND" in
  208. command)
  209. # Most systems should have printf, but the %q specifier isn't mandated by posix
  210. # and can't be guaranteed. Since this is mainly a cosmetic output and the alternative
  211. # is not making any attempt to do escaping at all, we might as well try.
  212. # shellcheck disable=SC2039
  213. quoted=$(printf '%q ' "$@" 2>/dev/null || true)
  214. # printf %q behaves oddly with zero args (it acts as though it received one empty arg)
  215. # so we ignore that case.
  216. if [ -z "$quoted" ] || [ $# -eq 0 ]; then
  217. quoted="$*"
  218. fi
  219. echo "$(launch_command) $quoted"
  220. ;;
  221. version)
  222. docker run --rm --entrypoint=/home/weave/scope "$SCOPE_IMAGE" --mode=version
  223. ;;
  224. -h | help | -help | --help)
  225. usage
  226. ;;
  227. launch)
  228. dry_run "$@"
  229. check_plugins_dir
  230. if check_docker_for_mac; then
  231. if check_probe_only; then
  232. launch "$@"
  233. exit
  234. fi
  235. # Docker for Mac (as of beta9) does not ship vmnet driver and
  236. # thereby only access container ports via a tunnel, preventing
  237. # access to host ports of the VM.
  238. # - https://github.com/weaveworks/scope/issues/1411
  239. # - https://forums.docker.com/t/ports-in-host-network-namespace-are-not-accessible/10789
  240. if check_listen_address_arg; then
  241. echo "--app.http.address argument not supported on Docker for Mac" >&2
  242. exit 1
  243. fi
  244. check_not_running "$SCOPE_APP_CONTAINER_NAME" "$SCOPE_IMAGE_NAME"
  245. check_not_running "$SCOPE_CONTAINER_NAME" "$SCOPE_IMAGE_NAME"
  246. docker rm -f "$SCOPE_APP_CONTAINER_NAME" >/dev/null 2>&1 || true
  247. CONTAINER=$($(launch_docker4mac_app_command) "$@")
  248. echo "Scope probe started"
  249. app_ip=$(docker inspect -f '{{.NetworkSettings.IPAddress}}' "${CONTAINER}")
  250. docker rm -f "$SCOPE_CONTAINER_NAME" >/dev/null 2>&1 || true
  251. # shellcheck disable=SC2091
  252. CONTAINER=$($(launch_command) --no-app "$@" "${app_ip}:4040")
  253. print_app_endpoints "localhost"
  254. exit
  255. fi
  256. launch "$@"
  257. if ! check_probe_only; then
  258. if check_listen_address_arg; then
  259. echo "Weave Scope is listening at the address specified with --app.http.address" >&2
  260. else
  261. wait_for_http
  262. IP_ADDRS=$(run_in_scope_container "$LISTENING_IP_ADDR_CMD")
  263. # shellcheck disable=SC2086
  264. print_app_endpoints $IP_ADDRS
  265. fi
  266. fi
  267. ;;
  268. stop)
  269. [ $# -eq 0 ] || usage_and_die
  270. if docker inspect "$SCOPE_CONTAINER_NAME" >/dev/null 2>&1; then
  271. docker stop "$SCOPE_CONTAINER_NAME" >/dev/null
  272. fi
  273. if check_docker_for_mac; then
  274. if docker inspect "$SCOPE_APP_CONTAINER_NAME" >/dev/null 2>&1; then
  275. docker stop "$SCOPE_APP_CONTAINER_NAME" >/dev/null
  276. fi
  277. fi
  278. ;;
  279. *)
  280. echo "Unknown scope command '$COMMAND'" >&2
  281. usage_and_die
  282. ;;
  283. esac