I wanted to follow up and mention that between Rarst's suggestion (WP now saves to post_content_filtered any time a new "container" page is created or updated) and Relevanssi's ability to include wp_posts columns in search results, I was able to achieve the desired results. Thank you so much, Rarst!
Edit:
To kaiser's point, my answer does nothing to help anyone in a similar situation. So, to add clarity, here's how it was resolved.
First, to help illustrate how a page would come together in this scenario, here's a little visual aid (below). I should note that the site also employs a plugin that enables drag and drop page layout. The challenge this presents comes into play later.

The following filter was added to functions.php and is triggered when a page is created or updated. It first checks to see if the page is a "container" page by the presence of the "pod" shortcode*, then grabs the page-specific content and strips out shortcodes generated by the page layout plugin. Next, it makes a list of the "pod" shortcodes, extracts the content from each, and adds it to the page-specific content. Finally, it makes one last pass to remove any HTML, then writes all of the text to the post's post_content_filtered field.
function write_content_pod_content ( $data, $postarr ) {
// If this is not an update or a pod container page, there's nothing to do
if ( ! isset($postarr[ 'ID' ] ) || ! $postarr[ 'ID' ] || ! has_shortcodes( 'pod' ) ) {
return $data;
} else {
$post_content = $postarr[ 'post_content' ]; // Get the container page's content
$search_data = preg_replace("~(?:\[/?)[^/\]]+/?\]~s", '', $post_content); // Strip shortcodes, keep shortcode content
$pod_id_array = has_shortcodes( 'pod', true ); // Get a list of the pod content page IDs
foreach( $pod_id_array as $pod_id ) {
$search_data .= do_shortcode( '[pod id=' . $pod_id . ']' );
}
// Write pod content to post_content_filtered field
$clean_data = wp_filter_nohtml_kses( $search_data ); // Remove all of the HTML, leaving only raw text
$data[ 'post_content_filtered' ] = $clean_data;
return $data;
}
}
add_filter('wp_insert_post_data', 'write_content_pod_content', 999, 2);
Lastly, in order to make any of this useful, the post_content_filtered database column had to be included in search. Turns out this was the least difficult step in the process thanks to Relevanssi. On the plugin's settings page, there's a field for defining additional database columns to index. For example (note: this functionality is only available in the Premium version of the the plugin):

*Because the shortcodes are added to the page using the drag and drop interface, I found it necessary to create a function for finding shortcodes within a page and/or their ID. I'm certain there are better ways to do this, so please excuse the lack of optimization, etc.
function has_shortcodes( $shortcode = '', $return_ids = false ) {
$post_to_check = get_post( get_the_ID() );
//False because we have to search through the post content first
$found = false;
//If no short code was provided, return false
if ( ! $shortcode ) {
return $found;
}
//Check the post content for the short code
$haystack = $post_to_check->post_content;
$needle = '[' . $shortcode;
$lastPos = 0;
$positions = array();
if ( stripos( $haystack, $needle ) !== false ) {
//Return an array containing the shortcode IDs
if ( $return_ids ) {
$pattern = '/\[\b' . $shortcode. '\b(.*?)\]/';
preg_match_all( $pattern, $haystack, $all_codes, PREG_PATTERN_ORDER );
$found = array();
for( $i = 0; $i < count( $all_codes[1]); $i++ ) {
$id_string_pattern = '/id\=\"(.*?)\"/';
preg_match( $id_string_pattern, $all_codes[1][$i], $id_string_matches, PREG_OFFSET_CAPTURE );
$found[] = $id_string_matches[1][0];
}
//Return true when nothing more than confirmation of finding shortcodes is needed
} else {
$found = true;
}
}
//Return our final results
return $found;
}
Hope that helps!