BreadcrumbHomeResourcesBlog C++ Microservices In Docker: Adding a Numerical Library April 22, 2021 C++ Microservices in Docker: Adding a Numerical LibraryEmbedded AnalyticsBy David HaneyThere are a variety of frameworks and tools available for implementing a microservice architecture, however, it isn’t always clear how to expose native code like C or C++ code within a wider microservice system. In this series, we have covered the basics of a C++ Microservices deployment including: Deploying HydraExpress into a Docker container.Deploying custom C++ Servlet instances.Optimizing a Docker container to minimize space.With those basics in place, this article will look at deploying a new C++ servlet that relies on the IMSL C Numerical Library (CNL), demonstrating how 3rd party libraries can be incorporated into a Docker application to support writing more advanced servlets.Create a Skeleton for the New ServiceFor this example we’ll be leveraging CNL as an example 3rd party library, however the same basic approach should be applicable to any 3rd party library you may want to rely on.We’ll be building on the Dockerfile that was assembled at the end of the Docker optimization article. (Go grab it now and follow along!)We’ll start by adding a skeleton for our new service to the build infrastructure and the Dockerfile. Similar to the “HelloWorldServlet” we’re currently deploying, we’ll create a new servlet context “random”. Within that we’ll create a new servlet “UniformServlet” that will return a uniform distribution of random numbers and use our existing “hello” context, servlet, and build infrastructure as a reference to create the new context. Those files (with the necessary changes from their “hello” counterparts highlighted) are outlined below:src/random/UniformServlet.cpp#include <string> #include <rwsf/servlet/ServletOutputStream.h> #include <rwsf/servlet/http/HttpServlet.h> #include <rwsf/servlet/http/HttpServletRequest.h> #include <rwsf/servlet/http/HttpServletResponse.h> class UniformServlet : public rwsf::HttpServlet { public: void doGet(rwsf::HttpServletRequest& request, rwsf::HttpServletResponse& response) { response.setContentType("text/html"); rwsf::ServletOutputStream& out = response.getOutputStream(); out.println("<html><body><h1>Uniform!</h1></body></html>"); } }; RWSF_DEFINE_SERVLET(UniformServlet)We’ll revisit the implementation of this servlet once we’ve deployed CNL, but for now we’ll just return a simple HTML message.src/random/WEB-INF/web.xml<web-app> <servlet> <servlet-name>Uniform</servlet-name> <servlet-class>random.createUniformServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Uniform</servlet-name> <url-pattern>/uniform</url-pattern> </servlet-mapping> </web-app>src/random/CMakeLists.txtcmake_minimum_required(VERSION 3.9) project(random VERSION 1.0 LANGUAGES CXX) add_library(random SHARED UniformServlet.cpp) target_compile_features(random PRIVATE cxx_auto_type) target_link_libraries(random RWSF::Servlet)src/CMakeLists.txtcmake_minimum_required(VERSION 3.9) project(servlets VERSION 1.0 LANGUAGES CXX) add_library(RWSF::Servlet SHARED IMPORTED) set_target_properties(RWSF::Servlet PROPERTIES IMPORTED_LOCATION $ENV{RWSF_HOME}/lib/librwsf_servlet20012d.so) target_compile_definitions(RWSF::Servlet INTERFACE -D_RWCONFIG=12d) target_include_directories(RWSF::Servlet INTERFACE $ENV{RWSF_HOME}/include) add_subdirectory(hello) add_subdirectory(random)We’ll also need to update our Dockerfile to deploy the new context to our container:Dockerfile… COPY --from=servlet_build /src/hello/WEB-INF ${RWSF_HOME}/apps/servlets/hello/WEB-INF/ COPY --from=servlet_build /build/random/librandom.so ${RWSF_HOME}/apps-lib/ COPY --from=servlet_build /src/random/WEB-INF ${RWSF_HOME}/apps/servlets/random/WEB-INF/ COPY entrypoint.sh /entrypoint.sh …Add the Numerical Library to a ContainerNow that we have skeleton for our new servlet in place, we can move on to incorporating IMSL C into our container.First, we need to download and install IMSL C, just as we did for HydraExpress. The process for the two is very similar, and they have similar requirements. We’ll start by creating a base image to avoid duplicating work between the two installations: DockerfileFROM centos:7 AS install RUN yum install -y wget RUN mkdir -p /opt/download FROM install AS hydraexpress_install … With our base image in place, we can replicate hydraexpress_install to install IMSL C. For this example we’ll deploy the IMSL C 2019 Evaluation release of the product. Similar to HydraExpress, deploying a different version or different variant of the product will require slightly different options when invoking the installer, but once installed the behavior and process should be the same. Since we’ll be using an evaluation version of IMSL C, you’ll need an evaluation license. If you’ve already requested an evaluation, the imsl_eval.dat file should be attached to your confirmation email.(If you need a license to try this contact us.)… RUN /opt/download/hydraexpress.run \ --mode unattended \ --prefix /opt/perforce/hydraexpress \ --license-file /opt/download/license.key FROM install as cnl_install RUN wget -q -O /opt/download/cnl.run \ https://dslwuu69twiif.cloudfront.net/imsl/cnl/2019/cnl-2019.0.0-lnxgc485x64_eval.run RUN chmod a+x /opt/download/cnl.run RUN /opt/download/cnl.run \ --mode unattended \ --prefix /opt/perforce COPY imsl_eval.dat /opt/perforce/license/imsl_eval.dat FROM centos:7 AS servlet_build … Update Servlet to Provide Random Numbers FunctionWith IMSL C now available, we can update our build script to incorporate IMSL C into our servlet to provide the function generating our random numbers. We’ll update our Uniform servlet so that it accepts a parameter, the number of random numbers it should generate, and returns that number of numbers. We’ll use IMSL’s imsl_d_random_uniform function to generate the random numbers that will be returned:src/random/UniformServlet.cpp… #include <rwsf/servlet/http/HttpServletResponse.h> #include <imsl.h> class UniformServlet : public rwsf::HttpServlet { public: void doGet(rwsf::HttpServletRequest& request, rwsf::HttpServletResponse& response) { auto count = std::stoi(request.getQueryString()); typedef std::unique_ptr<double[], void(*)(void*)> double_array; double_array values(imsl_d_random_uniform(count, 0), imsl_free); response.setContentType("text/plain"); auto& out = response.getOutputStream(); for (size_t i = 0; i < count; ++i) { out.println(values[i]); } } }; … We’ll also need to update our CMake configuration to include the IMSL C libraries. We’ll start with the root CMakeLists.txt to introduce the new libraries:src/CMakeLists.txt… target_include_directories(RWSF::Servlet INTERFACE $ENV{RWSF_HOME}/include) find_package(OpenMP) add_library(IMSL::CMath SHARED IMPORTED) set_target_properties(IMSL::CMath PROPERTIES IMPORTED_LOCATION $ENV{CNL_DIR}/$ENV{LIB_ARCH}/lib/libimslcmath_imsl.so) target_include_directories(IMSL::CMath INTERFACE $ENV{CNL_DIR}/$ENV{LIB_ARCH}/include) target_link_libraries(IMSL::CMath INTERFACE OpenMP::OpenMP_CXX) add_subdirectory(hello) … Next, we need to leverage the new imported library when building the random servlet context library:src/random/CMakeLists.txt… target_compile_features(random PRIVATE cxx_auto_type) target_link_libraries(random RWSF::Servlet IMSL::CMath) With our changes to our servlet complete, we can focus on updating the build step in our Dockerfile. Since we’re now depending on IMSL C, we’ll need to deploy it and configure the environment so that our Servlet can link against its libraries:Dockerfile… ENV RWSF_HOME /opt/perforce/hydraexpress ENV CNL_DIR /opt/perforce/imsl/cnl-2019.0.0 ENV LIB_ARCH lnxgc485x64 COPY --from=hydraexpress_install ${RWSF_HOME} ${RWSF_HOME} COPY --from=cnl_install ${CNL_DIR}/${LIB_ARCH} ${CNL_DIR}/${LIB_ARCH} RUN yum install -y epel-release … Deploy the Final ImageFinally, we can look towards deploying our new dependency to the final image. We need to copy the IMSL library that we depend on along with the evaluation license file to our final image.We also need to set the IMSL_LIC_FILE to allow IMSL C to locate the license file at runtime. Since our final image is based on a slim Alpine Linux image, we need to install an additional package, libgomp. We also need to add /lib64 to the LD_LIBRARY_PATH environment variable to allow dependencies to be found by the IMSL library.Dockerfile… FROM alpine:latest RUN apk update --no-cache && apk upgrade --no-cache && apk add --no-cache bash libstdc++ libc6-compat libgomp RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 ENV RWSF_HOME /opt/perforce/hydraexpress ENV IMSL_LIC_FILE /opt/perforce/license/imsl_eval.dat ENV LD_LIBRARY_PATH /lib64:$LD_LIBRARY_PATH COPY --from=hydraexpress_deploy ${RWSF_HOME} ${RWSF_HOME} COPY --from=servlet_build /build/hello/libhello.so ${RWSF_HOME}/apps-lib/ COPY --from=servlet_build /src/hello/WEB-INF ${RWSF_HOME}/apps/servlets/hello/WEB-INF/ COPY --from=servlet_build /build/random/librandom.so ${RWSF_HOME}/apps-lib/ COPY --from=servlet_build /src/random/WEB-INF ${RWSF_HOME}/apps/servlets/random/WEB-INF/ COPY --from=cnl_install /opt/perforce/imsl/cnl-2019.0.0/lnxgc485x64/lib/libimslcmath_imsl.so \ /opt/perforce/imsl/cnl-2019.0.0/lnxgc485x64/lib/libimslcmath_imsl.so COPY --from=cnl_install /opt/perforce/license /opt/perforce/license COPY entrypoint.sh /entrypoint.sh With the Dockerfile updated, we’re ready to rebuild and redeploy our C++ microservice:$ docker build -t hydraexpress . … $ docker run --rm -it -p 8090:8090 hydraexpress ******************************************************************************* RWSF (TM) - Server Control Script Copyright (c) 2001-2020 Rogue Wave Software, Inc., a Perforce company. All Rights Reserved. ******************************************************************************* RWSF_HOME = /opt/perforce/hydraexpress RWSP_HOME = /opt/perforce/hydraexpress/3rdparty/sourcepro Starting Rogue Wave Agent... INFO| Loading context: /hello/ INFO| Loading context: /random/ INFO| Locale directory set to [/opt/perforce/hydraexpress/conf/locale] INFO| Default locale set to [en_US] INFO| Loading locale [en_US], catalog [messages_en_US.xml] INFO| Starting 'HTTP/1.1' connector... Finally, we can use curl test our new random number generation, requesting five random numbers:$ curl http://localhost:8090/random/uniform?5 0.816719443451948 0.603686096893477 0.15223048867296 0.537823126436129 0.193286012016836 Providing Numeric Functionality in C++ MicroserviceWe’ve successfully deployed a C++ microservice that leverages IMSL C to provide numeric functionality. You can apply the same patterns to deploy other 3rd party libraries that may be necessary to fully implement our microservice and make it accessible to the other services in our infrastructure.Interested in trying this for yourself, request an evaluation copy of HydraExpress and IMSL today. Try IMSL Try HydraExpressRead Our Full Blog Series on C++ Microservices in Docker:Part 1: Expose Native C++ Code Within a Microservice.Part 2: Building Custom C++ Servlets.Part 3: Optimizing a Container Image.
David Haney Software Architect David has spent over 20 years leading the development efforts on SourcePro and HydraExpress, with a focus on flexible, safe and secure APIs and sustainable engineering practices. Most recently David has been applying that expertise to other products in the Perforce portfolio.