diff --git a/.github/workflows/build_and_scan_rocks.yaml b/.github/workflows/build_and_scan_rocks.yaml index 36bd05e..6c3306d 100644 --- a/.github/workflows/build_and_scan_rocks.yaml +++ b/.github/workflows/build_and_scan_rocks.yaml @@ -35,6 +35,7 @@ jobs: - mlserver-xgboost - seldon-core-operator - sklearnserver + - tensorflow-serving uses: canonical/charmed-kubeflow-workflows/.github/workflows/build_and_scan_rock.yaml@main secrets: JIRA_URL: ${{ secrets.CVE_REPORT_JIRA_URL }} diff --git a/tensorflow-serving/rockcraft.yaml b/tensorflow-serving/rockcraft.yaml new file mode 100644 index 0000000..5e09adc --- /dev/null +++ b/tensorflow-serving/rockcraft.yaml @@ -0,0 +1,116 @@ +# https://github.com/tensorflow/serving/blob/2.1.0/tensorflow_serving/tools/docker/Dockerfile +name: tensorflow-serving +summary: An image for Seldon Tensorflow Serving +description: | + This image is used as part of the Charmed Kubeflow product. +version: 2.13.0_20.04_1 # __ +license: Apache-2.0 +base: ubuntu:20.04 +run-user: _daemon_ +services: + tensorflow-serving: + override: replace + summary: "tensorflow-serving service" + startup: enabled + command: bash -c 'tensorflow_model_server --port=8500 --rest_api_port=8501 --model_name=${MODEL_NAME} --model_base_path=${MODEL_BASE_PATH}/${MODEL_NAME} "$@"' + environment: + MODEL_NAME: "model" + MODEL_BASE_PATH: "/models" +platforms: + amd64: + +parts: + tensorflow-serving: + plugin: nil + source: https://github.com/tensorflow/serving/ + source-type: git + source-tag: 2.13.0 + build-packages: + - automake + - build-essential + - ca-certificates + - curl + - git + - libcurl3-dev + - libfreetype6-dev + - libpng-dev + - libtool + - libzmq3-dev + - mlocate + - openjdk-8-jdk + - openjdk-8-jre-headless + - pkg-config + - pip + - python-dev + - python3.8 + - python3.8-dev + - python3-pip + - python3.8-venv + - software-properties-common + - swig + - tar + - unzip + - wget + - zip + - zlib1g-dev + - python3-distutils + build-environment: + - BAZEL_VERSION: "5.3.0" + override-build: | + set -e + + # Make python3.8 the default python version + python3.8 -m pip install pip --upgrade + update-alternatives --install /usr/bin/python python /usr/bin/python3.8 0 + + pip3 --no-cache-dir install future>=0.17.1 grpcio h5py keras_applications>=1.0.8 keras_preprocessing>=1.1.0 mock numpy portpicker requests --ignore-installed setuptools --ignore-installed six>=1.12.0 + + mkdir -p ${CRAFT_PART_INSTALL}/bazel + cd ${CRAFT_PART_INSTALL}/bazel + curl -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36" -fSsL -O https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh + curl -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36" -fSsL -o ${CRAFT_PART_INSTALL}/bazel/LICENSE.txt https://raw.githubusercontent.com/bazelbuild/bazel/master/LICENSE && \ + chmod +x bazel-*.sh + bash ./bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh + rm -f bazel/bazel-$BAZEL_VERSION-installer-linux-x86_64.sh + + # Build, and install TensorFlow Serving + export TF_SERVING_VERSION_GIT_COMMIT=HEAD + export TF_SERVING_BUILD_OPTIONS="--config=release" + echo "Building with build options: ${TF_SERVING_BUILD_OPTIONS}" + export TF_SERVING_BAZEL_OPTIONS="" + echo "Building with Bazel options: ${TF_SERVING_BAZEL_OPTIONS}" + export PATH=${PATH}:${CRAFT_PART_INSTALL}/bazel:/usr/local/bin:/usr/bin + + mkdir -p /root/.cache + chmod -R 766 /root/.cache + mkdir -p ${CRAFT_PART_INSTALL}/usr/local/bin/ + cd ${CRAFT_PART_SRC} + touch WORKSPACE + bazel build --color=yes --curses=yes ${TF_SERVING_BAZEL_OPTIONS} --verbose_failures --output_filter=DONT_MATCH_ANYTHING ${TF_SERVING_BUILD_OPTIONS} tensorflow_serving/model_servers:tensorflow_model_server + cp ${CRAFT_PART_SRC}/bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server ${CRAFT_PART_INSTALL}/usr/local/bin/ + + # Build and install TensorFlow Serving API + mkdir -p ${CRAFT_PART_INSTALL}/tmp/pip + bazel build --color=yes --curses=yes ${TF_SERVING_BAZEL_OPTIONS} --verbose_failures --output_filter=DONT_MATCH_ANYTHING ${TF_SERVING_BUILD_OPTIONS} tensorflow_serving/tools/pip_package:build_pip_package + ${CRAFT_PART_SRC}/bazel-bin/tensorflow_serving/tools/pip_package/build_pip_package ${CRAFT_PART_INSTALL}/tmp/pip + pip --no-cache-dir install --upgrade ${CRAFT_PART_INSTALL}/tmp/pip/tensorflow_serving_api-*.whl + rm -rf /tmp/pip + + # Clean up Bazel cache when done. + bazel clean --expunge --color=yes + rm -rf /root/.cache/bazel + + organize: + usr/local/bin/tensorflow_model_server: usr/bin/tensorflow_model_server + + security-requirements: + plugin: nil + after: [tensorflow-serving] + override-build: | + # security requirement using `ubuntu-22.04` base + # > ${CRAFT_PART_INSTALL}/usr/share/rocks/dpkg.query + # `--root` option is not available in dpkg-query version which is packaged with 20.04 + mkdir -p ${CRAFT_PART_INSTALL}/usr/share/rocks + (echo "# os-release" && cat /etc/os-release && echo "# dpkg-query") \ + > ${CRAFT_PART_INSTALL}/usr/share/rocks/dpkg.query + diff --git a/tensorflow-serving/tox.ini b/tensorflow-serving/tox.ini new file mode 100644 index 0000000..0ec0c62 --- /dev/null +++ b/tensorflow-serving/tox.ini @@ -0,0 +1,76 @@ +# Copyright 2022 Canonical Ltd. +# See LICENSE file for licensing details. +[tox] +skipsdist = True +skip_missing_interpreters = True + +[testenv] +setenv = + PYTHONPATH={toxinidir} + PYTHONBREAKPOINT=ipdb.set_trace + CHARM_REPO=https://github.com/canonical/seldon-core-operator.git + CHARM_BRANCH=main + LOCAL_CHARM_DIR=charm_repo + +[testenv:unit] +passenv = * +allowlist_externals = + bash + tox + rockcraft +deps = + juju~=2.9.0 + pytest + pytest-operator + ops + charmed_kubeflow_chisme +commands = + # build and pack rock + rockcraft pack + bash -c 'NAME=$(yq eval .name rockcraft.yaml) && \ + VERSION=$(yq eval .version rockcraft.yaml) && \ + ARCH=$(yq eval ".platforms | keys" rockcraft.yaml | awk -F " " '\''{ print $2 }'\'') && \ + ROCK="$\{NAME\}_$\{VERSION\}_$\{ARCH\}" && \ + sudo skopeo --insecure-policy copy oci-archive:$ROCK.rock docker-daemon:$ROCK:$VERSION' + + # run rock tests + pytest -vvv --tb native --show-capture=all --log-cli-level=INFO {posargs} {toxinidir}/tests + +[testenv:integration] +passenv = * +allowlist_externals = + bash + git + rm + tox + rockcraft + sed +deps = + juju~=2.9.0 + pytest + pytest-operator + ops +commands = + # build and pack rock + rockcraft pack + # clone related charm + rm -rf {env:LOCAL_CHARM_DIR} + git clone --branch {env:CHARM_BRANCH} {env:CHARM_REPO} {env:LOCAL_CHARM_DIR} + # replace jinja2 templated value with yq safe placeholder + sed -i "s/namespace: {{ namespace }}/namespace: YQ_SAFE/" {env:LOCAL_CHARM_DIR}/src/templates/configmap.yaml.j2 + # upload rock to docker and microk8s cache, replace charm's container with local rock reference + bash -c 'NAME=$(yq eval .name rockcraft.yaml) && \ + VERSION=$(yq eval .version rockcraft.yaml) && \ + ARCH=$(yq eval ".platforms | keys" rockcraft.yaml | awk -F " " '\''{ print $2 }'\'') && \ + ROCK="$\{NAME\}_$\{VERSION\}_$\{ARCH\}" && \ + sudo skopeo --insecure-policy copy oci-archive:$ROCK.rock docker-daemon:$ROCK:$VERSION && \ + docker save $ROCK > $ROCK.tar && \ + microk8s ctr image import $ROCK.tar --digests=true && \ + predictor_servers=$(yq e ".data.predictor_servers" {env:LOCAL_CHARM_DIR}/src/templates/configmap.yaml.j2) && \ + predictor_servers=$(jq --arg jq_rock $ROCK -r '\''.TENSORFLOW_SERVER.protocols.v2.image=$jq_rock'\'' <<< $predictor_servers) && \ + predictor_servers=$(jq --arg jq_version $VERSION -r '\''.TENSORFLOW_SERVER.protocols.v2.defaultImageVersion=$jq_version'\'' <<< $predictor_servers) yq e -i ".data.predictor_servers=strenv(predictor_servers)" {env:LOCAL_CHARM_DIR}/src/templates/configmap.yaml.j2' + # replace yq safe placeholder with original value + sed -i "s/namespace: YQ_SAFE/namespace: {{ namespace }}/" {env:LOCAL_CHARM_DIR}/src/templates/configmap.yaml.j2 + # run charm integration test with rock + tox -c {env:LOCAL_CHARM_DIR} -e integration +