This blog describes the detail steps to create a customer software specification for Watson Machine Learning in Cloud Pak for Data v4
Step 1: Preparation
You may build a custom image in bastion node of your air-gapped CPD cluster.
Pull out a terminal on bastion node
export cpd_url=<url of CPD console, eg. https://cpd-cpd-instance1.apps.bj-prod-16.luban.cdl.ibm.com>
export cpd_py_runtime_file=wml-deployment-runtime-py39-server.json
export PRIVATE_REGISTRY_LOCATION=<your private container registry, eg. bastion.bj-prod-18.luban.cdl.ibm.com:15000>
export PRIVATE_REGISTRY_PULL_USER=<user_name>
export PRIVATE_REGISTRY_PULL_PASSWORD=<password_of_user>
Step 2: Download default runtime configuration
Replace the <user_name:password>
with your CPD login user name and password to get token
myToken=`curl -k ${cpd_url}/v1/preauth/validateAuth -u <user_name:password> | sed -n -e 's/^.*accessToken":"//p' | cut -d'"' -f1`
curl -X GET \
"${cpd_url}/zen-data/v1/volumes/files/%2F_global_%2Fconfig%2F.runtime-definitions%2Fibm%2F${cpd_py_runtime_file}" \
--header "Authorization: Bearer ${myToken}" \
-k > ${cpd_py_runtime_file}
Step 3: Pull default runtime image
cat ${cpd_py_runtime_file} | grep image
Example of the output:
"image": "cp.icr.io/cp/cpd/wml-deployment-runtime-py39-1@sha256:c604bf14c3b9d137a0632d2323c234d9da279d31e3bd8e214f6c1a5b6175d3de",
...
Copy out the image address and replace it with your private contianer registry address, eg. bastion.bj-prod-18.luban.cdl.ibm.com:15000/cp/cpd/wml-deployment-runtime-py39-1@sha256:c604bf14c3b9d137a0632d2323c234d9da279d31e3bd8e214f6c1a5b6175d3de
podman login -u ${PRIVATE_REGISTRY_PULL_USER} -p ${PRIVATE_REGISTRY_PULL_PASSWORD} ${PRIVATE_REGISTRY_LOCATION}
podman pull bastion.bj-prod-18.luban.cdl.ibm.com:15000/cp/cpd/wml-deployment-runtime-py39-1@sha256:c604bf14c3b9d137a0632d2323c234d9da279d31e3bd8e214f6c1a5b6175d3de
Step 4: build a custom image
Create a Dockerfile
Content of the Dockerfile, in this example, pyecharts==1.9.1
is the package added, you can add more packages necessarily.
ARG base_image_tag
FROM ${base_image_tag}
# For installing OS packages, use root:root
# e.g.
USER root:root
RUN yum install -y gdb
# For installing to conda site-packages,
# use wmlfuser:condausers
# e.g.
USER wmlfuser:condausers
RUN umask 002 && \
pip install gensim \
pyecharts==1.9.1
#
# Alternatively, for Installing to location other than conda site-packages.
# It can be installed to
# --target <any_file_system_dir_path>
# Provided <any_file_system_dir_path> full path is accessible to any user with group "condausers"
#
# Important:
# The <any_file_system_path> must then be added to the environment variable PYTHONPATH
#
# e.g
# use wmlfuser:condausers to install to the path /home/wmlfuser/mypkgs and add
# the path to PYTHONPATH environment variable
#
USER wmlfuser:condausers
RUN umask 002 && \
mkdir -p /home/wmlfuser/mypkgs && \
pip install pydot --target /home/wmlfuser/mypkgs
ENV PYTHONPATH=$PYTHONPATH:/home/wmlfuser/mypkgs
# If pip --user is used for installing package.
# The installation path needs to be explicitly added to PYTHONPATH
# e.g.
#
# for user wmlfuser:condausers it by default goes to user install directory
# (See the Python documentation for site.USER_BASE for full details.)
# use `python -m site --user-site` command to get the path.
#
# for wml-deployment-runtime-py39-1, it goes to
# /home/wmlfuser/.local/lib/python3.9/site-packages
#
#
Build the custom image, in this example, custom_image:image_tage is wml-cust-runtime-pyecharts:1.9.1
podman build -t wml-cust-runtime-pyecharts:1.9.1 \
--build-arg base_image_tag=bastion.bj-prod-18.luban.cdl.ibm.com:15000/cp/cpd/wml-deployment-runtime-py39-1@sha256:c604bf14c3b9d137a0632d2323c234d9da279d31e3bd8e214f6c1a5b6175d3de \
-f ./Dockerfile
Step 5: Push custom image
podman tag wml-cust-runtime-pyecharts:1.9.1 bastion.bj-prod-18.luban.cdl.ibm.com:15000/cp/cpd/wml-cust-runtime-pyecharts:1.9.1
podman push bastion.bj-prod-18.luban.cdl.ibm.com:15000/cp/cpd/wml-cust-runtime-pyecharts:1.9.1
Step 6: Creating a custom software specification
Create a volume instance, PROJECT_CPD_INSTANCE
is OCP project installed CP4D
export PROJECT_CPD_INSTANCE=cpd-instance1
export VSNAME=CCHome
curl -k -X POST \
${cpd_url}/zen-data/v3/service_instances \
-H "Authorization: Bearer ${myToken}" \
-H "Content-Type: application/json" \
-d "{
\"addon_type\": \"volumes\", \
\"addon_version\": \"-\", \
\"create_arguments\": { \
\"metadata\": {
\"existing_pvc_name\": \"cc-home-pvc\" \
} \
}, \
\"namespace\": \"${PROJECT_CPD_INSTANCE}\", \
\"display_name\": \"${VSNAME}\" \
}"
Check whether the new Volume instance is available
curl -k \
${cpd_url}/zen-data/v3/service_instances \
-H "Authorization: Bearer ${myToken}" \
-H 'Content-Type: application/json' | jq '.service_instances[] | .display_name,.id,.metadata.existing_pvc_name'
Example of output
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 7607 0 7607 0 0 9121 0 --:--:-- --:--:-- --:--:-- 9110
"cpd-instance1::CCHome"
"1670411879751802"
"cc-home-pvc"
"default-openpages-instance"
"1668058558932671"
Stop the file server for a volume (In case it is started by default)
curl -k -X DELETE \
${cpd_url}/zen-data/v1/volumes/volume_services/${PROJECT_CPD_INSTANCE}::${VSNAME} \
-H "Authorization: Bearer ${myToken}"
Output message {"_messageCode_":"200","message":"Successfully stopped volume service"}
Or {"_statusCode_":404,"exception":"deployment cchome-deploy does not exist","message":"Not Found"}
means the file server is not active yet.
Start the file server for a volume
curl -k -X POST \
${cpd_url}/zen-data/v1/volumes/volume_services/${PROJECT_CPD_INSTANCE}::${VSNAME} \
-H "Authorization: Bearer ${myToken}" \
-H 'Content-Type: application/json' \
-d '{}'
Output message {"_messageCode_":"200","message":"Successfully started volume service"}
Create custom software specification
SW_SPEC_NAME='pyecharts_py39'
DISPLAY_NAME='Pyecharts custom image'
SW_SPEC_JSON=${SW_SPEC_NAME}.json
PATH_SW_SPEC_JSON=`pwd`"/"$SW_SPEC_JSON
echo $PATH_SW_SPEC_JSON
echo '{
"metadata": {
"name": "'"$SW_SPEC_NAME"'",
"description": "Test"
},
"entity": {
"software_specification": {
"type": "base",
"built_in": false,
"package_extensions": [],
"display_name": "'"$DISPLAY_NAME"'",
"software_configuration": {
"included_packages": [
{
"name": "pyecharts",
"version": "1.9.1"
}
],
"platform": {
"name": "python",
"version": "3.9"
}
}
}
}
}' > $SW_SPEC_JSON
Check out the content
Upload custom software specification
curl -k -X PUT \
${cpd_url}/zen-volumes/${VSNAME}/v1/volumes/files/%2F_global_%2Fconfig%2Fenvironments%2Fsoftware-specifications%2F${SW_SPEC_JSON} \
-H "Authorization: Bearer ${myToken}" \
-H 'content-type: multipart/form-data' \
-F upFile=@"${PATH_SW_SPEC_JSON}"
Output message {"_messageCode_":"Success","message":"Successfully uploaded file and created the directory structure"}
Check out custom software specification is uploaded
curl -k -X GET \
${cpd_url}/zen-volumes/${VSNAME}/v1/volumes/files/%2F_global_%2Fconfig%2Fenvironments%2Fsoftware-specifications%2F${SW_SPEC_JSON} \
-H "Authorization: Bearer ${myToken}"
Restart the file server
curl -k -X DELETE \
${cpd_url}/zen-data/v1/volumes/volume_services/${PROJECT_CPD_INSTANCE}::${VSNAME} \
-H "Authorization: Bearer ${myToken}"
Output message {"_messageCode_":"200","message":"Successfully stopped volume service"}
curl -k -X POST \
${cpd_url}/zen-data/v1/volumes/volume_services/${PROJECT_CPD_INSTANCE}::${VSNAME} \
-H "Authorization: Bearer ${myToken}" \
-H 'Content-Type: application/json' \
-d '{}'
Output message {"_messageCode_":"200","message":"Successfully started volume service"}
Check out custom software specification is available from a REST endpoint
curl -k -X GET ${cpd_url}/v2/software_specifications?name=${SW_SPEC_NAME} -H "Authorization: Bearer ${myToken}" | jq "."
Output
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 601 100 601 0 0 3320 0 --:--:-- --:--:-- --:--:-- 3320
{
"total_results": 1,
"resources": [
{
"metadata": {
"created_at": "2022-12-07T12:01:36.691Z",
"updated_at": "2022-12-07T12:01:36.691Z",
"name": "pyecharts_py39",
"description": "Test",
"asset_type": "software_specification",
"href": "/v2/software_specifications/b2590c9b-343f-57f0-a8d3-29bf270a7e93",
"asset_id": "b2590c9b-343f-57f0-a8d3-29bf270a7e93"
},
"entity": {
"software_specification": {
"type": "base",
"built_in": false,
"package_extensions": [],
"display_name": "Pyecharts custom image",
"software_configuration": {
"included_packages": [
{
"name": "pyecharts",
"version": "1.9.1"
}
],
"platform": {
"name": "python",
"version": "3.9"
}
}
}
}
}
]
}
Step 7: Upload custom runtime configuration
The value of image
is the custom image pushed in step 5
Limitation: The filename needs to end with *-server.json
CUSTOM_WML_RUN_DEF=wml-$SW_SPEC_NAME-server.json
echo ${CUSTOM_WML_RUN_DEF}
echo '{
"displayName": "WML custom image sample",
"description": "description for WML custom image sample RD",
"author": "IBM",
"tested": true,
"isService": true,
"features": ["wml"],
"runtimeType": "wml",
"software_specification_name": "'"$SW_SPEC_NAME"'",
"image": "bastion.bj-prod-18.luban.cdl.ibm.com:15000/cp/cpd/wml-cust-runtime-pyecharts:1.9.1"
}' > $CUSTOM_WML_RUN_DEF
Upload the custom runtime definition file
curl -k -X PUT \
${cpd_url}/zen-data/v1/volumes/files/%2F_global_%2Fconfig%2F.runtime-definitions%2Fibm \
-H "Authorization: Bearer ${myToken}" \
-H 'content-type: multipart/form-data' \
-F upFile=@"${CUSTOM_WML_RUN_DEF}"
Check out custom runtime definition file is on the volume
curl -k -X GET \
${cpd_url}/zen-data/v1/volumes/files/%2F_global_%2Fconfig%2F.runtime-definitions%2Fibm%2F${CUSTOM_WML_RUN_DEF} \
--header "Authorization: Bearer ${myToken}"
Check out custom runtime definition is available in REST endpoint
curl -k -s -X GET \
${cpd_url}/v2/runtime_definitions \
-H "Authorization: Bearer ${myToken}"
Step 8: Use the custom software specification to deploy a python function
Deploy a python function from a Jupyter Notebook
#wml_python_function
def my_deployable_function():
def score( payload ):
message_from_input_payload = payload.get("input_data")[0].get("values")[0][0]
response_message = "Received message - {0}".format(message_from_input_payload)
# Score using the pre-defined model
score_response = {
'predictions': [{'fields': ['Response_message_field'],
'values': [[response_message]]
}]
}
return score_response
return score
from ibm_watson_machine_learning import APIClient
from project_lib.utils import environment
url = environment.get_common_api_url()
import sys,os,os.path
token = os.environ['USER_ACCESS_TOKEN']
wml_credentials = {
"instance_id": "openshift",
"token": token,
"url": url,
"version": "4.5"
}
client = APIClient(wml_credentials)
space_name = 'Pyecharts-Space'
space_uid = ''
for space in client.spaces.get_details()['resources']:
if space['entity']['name'] ==space_name:
space_uid=space['metadata']['id']
if space_uid == '':
space_meta_data = {
client.spaces.ConfigurationMetaNames.NAME : space_name
}
stored_space_details = client.spaces.store(space_meta_data)
space_uid = stored_space_details['metadata']['id']
client.set.default_space(space_uid)
sw_spec_id = client.software_specifications.get_id_by_name('pyecharts_py39')
function_meta = {
client.repository.FunctionMetaNames.NAME:"pyecharts_function",
client.repository.FunctionMetaNames.SOFTWARE_SPEC_ID: sw_spec_id
}
func_details = client.repository.store_function(my_deployable_function, function_meta)
Go to the deployment space and should be able to see the pyecharts_function is listed as custom software specification
Deploy the python function as online service with custom software specification
And deployment is succeed
You may refer CPD KC on this topic: https://www.ibm.com/docs/en/cloud-paks/cp-data/4.5.x?topic=functions-working-custom-images
#data-featured-area-3
#data-spotlight